crefft Source 2026.10.4

Claude Code CLI Configuration

crefft > AI > Claude Code CLI

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Overview

This guide covers Claude Code CLI1 configuration: hooks, permissions, subagents, commands, and MCP. For universal AI workflows (Hero Flow, TDD, Visual Iteration), see AI Workflows.

Quick Reference

TaskCommand/File
Start sessionclaude
Plan mode/plan
Headless modeclaude -p "prompt"
Permissions/permissions
Clear context/clear
Config file.claude/settings.json
Hook scripts.claude/hooks/*.sh
Subagents.claude/agents/*.md
Commands.claude/commands/*.md
MCP servers.mcp.json

Table of Contents

  1. Configuration Files
  2. Hooks
  3. Permissions
  4. Subagents
  5. Custom Commands
  6. MCP Integration
  7. Long-Running Sessions
  8. Syncing from crefft

Configuration Files

Claude Code uses several configuration files:

project/
├── .claude/
│   ├── settings.json      # Permissions, hooks, preferences
│   ├── hooks/             # Hook scripts invoked from settings.json
│   │   ├── check-file.sh
│   │   └── verify-complete.sh
│   ├── agents/            # Reusable subagent definitions
│   │   ├── code/          # Code quality agents
│   │   │   ├── architect.md
│   │   │   ├── reviewer.md
│   │   │   └── ...
│   │   ├── system/        # Infrastructure agents
│   │   │   ├── architect.md
│   │   │   ├── docker.md
│   │   │   └── ...
│   │   └── security/      # Security agents
│   │       └── ...
│   └── commands/          # Custom slash commands
│       ├── code.md
│       ├── system.md
│       └── ...
├── .mcp.json              # MCP server configuration
├── AGENTS.md              # Project context (see agents-md.md)
├── CLAUDE.md              # `@AGENTS.md` import
└── GEMINI.md              # `@./AGENTS.md` import

settings.json Structure

{
  "permissions": {
    "allow": [...],
    "deny": [...]
  },
  "hooks": {
    "PostToolUse": [...],
    "PreToolUse": [...],
    "Stop": [...]
  },
  "preferences": {
    "autoCompact": true,
    "compactThreshold": 100000
  }
}

See configs/claude/settings.json for crefft’s standard configuration.


Hooks

Hooks automate quality checks and enable long-running sessions. They MUST be configured for professional workflows.

Hook Types

HookWhen it RunsUse Case
PostToolUseAfter Claude uses a toolAuto-format, auto-lint
PreToolUseBefore Claude uses a toolValidation, logging
StopWhen Claude stopsVerify completion, auto-resume

Hook Exit Codes

Exit codes decide whether a hook is a gate or a comment. Claude Code treats exit 2 as the blocking status; every other non-zero code is a non-blocking error that lets the action proceed2.

Exit codeEffect on PostToolUseEffect on Stop
0Success. stderr goes to the debug log only; Claude never sees itClaude stops
1 (or any other non-zero)Non-blocking error, stderr not shown to ClaudeClaude stops anyway
2Tool already ran, but Claude sees stderrBlocks the stop; stderr becomes the reason to continue

Hooks MUST NOT end a check with || true and MUST NOT rely on exit code 1 to gate anything.

Why

|| true replaces the check’s status with 0, and exit 1 is non-blocking, so both turn a failed check into a silent success:

# Don't: both of these report success to Claude Code
sh -c 'npm run lint || true'        # exits 0 even when lint fails
sh -c 'npm test && npm run lint'    # exits 1, which does not block Stop

# Do: report the reason on stderr and exit 2
printf 'lint failed on src/auth.ts\n' >&2
exit 2

PostToolUse: Auto-Format

MUST auto-format after file changes. This catches the “last 10%” of style issues, and MUST surface a formatter failure to Claude instead of discarding it:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-file.sh",
            "args": ["npm", "run", "format", "--", "--write"]
          }
        ]
      }
    ]
  }
}

check-file.sh runs the command it is given against the file Claude just wrote, and reports a failure the only way PostToolUse can — stderr plus exit 2:

#!/usr/bin/env bash
# .claude/hooks/check-file.sh - run "<argv> <edited file>" after Write or Edit.
set -uo pipefail

file=$(jq -r '.tool_input.file_path // empty')
[ -n "$file" ] || exit 0

if ! output=$("$@" "$file" 2>&1); then
  printf '%s failed on %s:\n%s\n' "$*" "$file" "$(tail -n 20 <<<"$output")" >&2
  exit 2
fi

exit 0

Make it executable with chmod +x .claude/hooks/check-file.sh.

Why

args selects Claude Code’s exec form, so the script path and each argument are passed verbatim with no shell tokenization — the correct form whenever a command references a path placeholder such as ${CLAUDE_PROJECT_DIR}2. Reading .tool_input.file_path into a quoted variable keeps paths containing spaces intact. Truncating to the last 20 lines keeps the feedback short enough to be actionable.

PostToolUse: Auto-Lint

SHOULD lint after file changes, using the same script:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-file.sh",
            "args": ["npm", "run", "lint:file", "--"]
          }
        ]
      }
    ]
  }
}

A per-file lint hook is a fast signal, not proof that the change is clean. The whole-change gate below MUST still run.

Stop: Verify Completion

MUST verify before accepting completion on important tasks. A Stop hook that runs the gate commands directly does not work: the shell reports exit 1 on failure, which Claude Code treats as non-blocking. Call a script that exits 2 instead:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/verify-complete.sh",
            "args": []
          }
        ]
      }
    ]
  }
}
#!/usr/bin/env bash
# .claude/hooks/verify-complete.sh - Stop hook completion gate.
set -uo pipefail

hook_input=$(cat)

# Claude Code overrides the hook after 8 consecutive blocks; stop asking
# once a previous block already resumed the turn.
if [ "$(jq -r '.stop_hook_active // false' <<<"$hook_input")" = "true" ]; then
  exit 0
fi

failures=""

run_gate() {
  local label=$1
  shift
  local output
  if ! output=$("$@" 2>&1); then
    failures+="${label} failed:"$'\n'"$(tail -n 20 <<<"$output")"$'\n\n'
  fi
}

run_gate "npm test" npm test
run_gate "npm run lint" npm run lint
run_gate "npm run typecheck" npm run typecheck

if [ -n "$failures" ]; then
  printf '%s' "$failures" >&2
  exit 2
fi

exit 0

Why

Every gate runs, so one failing suite does not hide the next; the stderr text becomes the reason Claude is shown for continuing. The stop_hook_active check prevents a gate that can never pass from looping, and Claude Code caps continuations at eight consecutive blocks regardless2. A Stop hook is therefore a strong gate, not a guarantee that Claude never stops early.

Hooks MAY instead exit 0 and print blocking JSON on stdout; Claude receives the reason exactly as it receives exit-2 stderr2:

{
  "decision": "block",
  "reason": "npm test failed: 2 failing in src/auth.test.ts"
}

Make the script executable with chmod +x .claude/hooks/verify-complete.sh. A gate whose script is missing or not executable exits 127, which Claude Code reports as a non-blocking error — the session stops with the gate silently disabled2.

For a completion condition that applies to one session rather than every session in the project, SHOULD use /goal3 instead of editing settings.json.

Hook Input

Hook commands receive a JSON payload on stdin (there are no $CLAUDE_* environment variables for tool data). Extract fields with jq:

JSON fieldValueEvent
.tool_input.file_pathPath to file being written/editedPostToolUse
.tool_nameName of tool being usedPreToolUse, PostToolUse
.stop_hook_activetrue when a stop hook already resumed the turnStop

Example: file=$(jq -r '.tool_input.file_path // empty')


Permissions

MUST configure permissions to avoid prompt fatigue without sacrificing safety.

Permission Syntax

ToolName(pattern)

Examples:

  • Bash(npm run build) — Allow exactly npm run build
  • Bash(npm run *) — Allow every script package.json defines
  • Edit(src/**) — Allow file writes/edits in src/ and subdirectories (file-tool path rules are matched as Edit(path); Write(path) rules are not matched and are dead)
  • Read(~/.ssh/**) — Match everything under the home directory’s .ssh

Rules are evaluated deny, then ask, then allow, and the first match wins. A deny rule therefore cannot carry allowlist exceptions, and an ask rule prompts even when a narrower allow rule also matches.3

crefft’s Standard Allowlist

Allow rules MUST name reviewed commands. They MUST NOT pre-approve a package runner or environment runner, because those execute whatever argument follows them:

{
  "permissions": {
    "allow": [
      "Bash(npm run build)",
      "Bash(npm run lint)",
      "Bash(npm run typecheck)",
      "Bash(npm test *)",
      "Bash(git status *)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Bash(git show *)",
      "Bash(git branch *)",
      "Bash(rg *)",
      "Edit(src/**)",
      "Edit(tests/**)",
      "Edit(test/**)",
      "Edit(docs/**)"
    ]
  }
}

Why

No blanket npx. Claude Code strips a fixed set of wrappers before matching a Bash rule, and environment runners such as npx, docker exec, and devbox run are deliberately not in that set: a rule matches whatever follows the runner, so Bash(npx *) pre-approves any package the registry will serve.3 Approve the inner command instead, one rule per command.

Package scripts belong to the repository. Bash(npm run *) approves every script package.json defines now and every script a branch, a dependency bump, or a pull request adds later. Name the scripts you reviewed. A team MAY widen this to Bash(npm run *) for a repository whose manifest it owns and reviews; it MUST NOT do so for code it has not reviewed.

No blanket Read, Glob, or Grep. Read-only tools already run without approval inside the working directory and additional directories,3 so Read(*) adds nothing there and grants the rest of the filesystem, including ~/.aws and ~/.ssh.

Narrow git mutations. Bash(git *) matches every subcommand and every option before it, including git -c core.fsmonitor=<script> diff, which runs a program of the caller’s choosing.3 Allow read-only subcommands; route add, commit, checkout, and push through ask rules.

Ask Rules for Mutation and Network

Commands that change history or reach the network SHOULD prompt rather than run unattended:

{
  "permissions": {
    "ask": [
      "Bash(git add *)",
      "Bash(git commit *)",
      "Bash(git checkout *)",
      "Bash(git push *)",
      "Bash(npm install *)",
      "Bash(npx *)",
      "Bash(curl *)",
      "Bash(wget *)",
      "Bash(ssh *)"
    ]
  }
}

Argument-level Bash patterns are fragile — Bash(curl https://github.com/ *) misses curl -X GET, a redirect, or URL=… && curl $URL — so gate the tool and use WebFetch(domain:example.com) for reviewed domains instead of trying to constrain URLs inside a shell rule.3

crefft’s Standard Denylist

Secret material MUST be denied for reads as well as edits:

{
  "permissions": {
    "deny": [
      "Bash(rm -rf *)",
      "Bash(sudo *)",
      "Bash(curl * | bash)",
      "Bash(wget * | bash)",
      "Bash(chmod 777 *)",
      "Bash(> /dev/sd*)",
      "Read(.env)",
      "Read(.env.*)",
      "Read(**/secrets/**)",
      "Read(**/*secret*)",
      "Read(**/*password*)",
      "Read(**/*credential*)",
      "Read(**/*token*)",
      "Read(**/*.pem)",
      "Read(**/*.key)",
      "Read(**/id_rsa*)",
      "Read(~/.aws/**)",
      "Read(~/.ssh/**)",
      "Edit(.env)",
      "Edit(.env.*)",
      "Edit(**/secrets/**)",
      "Edit(**/*secret*)",
      "Edit(**/*password*)",
      "Edit(**/*credential*)",
      "Edit(**/*token*)",
      "Edit(**/*.pem)",
      "Edit(**/*.key)",
      "Edit(**/id_rsa*)"
    ]
  }
}

Why

A Read deny rule blocks Claude’s file tools, the file commands Claude Code recognizes in Bash such as cat, head, and sed, and Bash redirection targets. It also blocks Edit from v2.1.208 and Write from v2.1.228, but it never covers NotebookEdit, so keep the paired Edit denies.3 Bare filenames follow gitignore semantics: Read(.env) and Read(**/.env) are equivalent and match at any depth under the current directory.3

Permission rules are not an OS boundary. They do not constrain a subprocess that opens a file itself — a Python or Node script Claude runs reads ~/.aws/credentials regardless of any Read deny. Pair this policy with the sandbox, which enforces filesystem and network limits at the OS level.4

Managing Permissions

# Interactive permission management
/permissions

# View current permissions
/permissions list

# Add permission for session
/permissions allow Bash(docker compose *)

Subagents

Subagents are specialized Claude instances organized into agent families. Each family has an Opus coordinator and Sonnet specialists.

Agent Families

crefft provides three agent families:

FamilyCommandCoordinatorPurpose
Code/codeCode Architect (Opus)Code quality review
System/systemSystem Architect (Opus)Infrastructure review
Security/securitySecurity Architect (Opus)Security analysis

See the family guides for full details:

Code Agent Family (/code)

AgentFilePurpose
Code Architectcode/architect.mdOpus coordinator
Code Reviewercode/reviewer.mdGeneral code review
Performancecode/performance.mdDeep performance analysis
Accessibilitycode/accessibility.mdWCAG/A11y compliance
REST APIcode/api-rest.mdREST API design
GraphQL APIcode/api-graphql.mdGraphQL schema design
Test Writercode/test-writer.mdTest generation
Simplifiercode/simplifier.mdComplexity reduction

System Agent Family (/system)

AgentFilePurpose
System Architectsystem/architect.mdOpus coordinator
Dockersystem/docker.mdContainer security
Ansiblesystem/ansible.mdPlaybook review
Linuxsystem/linux.mdOS hardening
Verifysystem/verify.mdCI/CD validation

Unique Differentiators

crefft’s agents are the most comprehensive open-source option:

  1. Tiered architecture — Opus coordinators, Sonnet specialists, cost optimized
  2. Accessibility reviews — No competitor offers WCAG/A11y analysis
  3. API design reviews — REST and GraphQL specialists
  4. Infrastructure reviews — Docker, Ansible, Linux specialists
  5. crefft integration — References our style guides
  6. Multi-LLM compatible — Works with Claude, GPT, Gemini
  7. Auto-fix suggestions — Every issue includes fix, not just complaint

Using Agents

# Full code assessment (Opus coordinates specialists)
/code src/auth/

# Quick review (Haiku, critical only)
/code quick

# Specific specialist
/code perf src/services/

# Full infrastructure assessment
/system stacks/

# Specific system review
/system docker stacks/platform/

Custom Commands

Custom slash commands are reusable prompt templates.

Creating Commands

Store in .claude/commands/:

# .claude/commands/code.md

Analyze the code at: $ARGUMENTS

Based on subcommand, route to appropriate agent:
- (none) → Full assessment with Code Architect
- quick → Critical issues only (Haiku)
- review → Standard code review (Sonnet)
- perf → Performance Reviewer
- a11y → Accessibility Reviewer
...

Using Commands

/code src/auth/
# Full assessment with Code Architect

/code quick
# Quick review of staged changes

/code perf src/services/
# Performance-focused review

crefft’s Standard Commands

CommandPurposeSubcommands
/codeCode quality reviewquick, review, perf, a11y, api, tests, simplify, docs
/systemInfrastructure reviewdocker, ansible, linux, verify
/securitySecurity analysisSee Security Agent Family
/refactorRefactor code--explain, --metrics, --incremental, --safe
/explainExplain code--deep
/planCreate implementation planFeature description

See commands/ for full definitions.


MCP Integration

Model Context Protocol (MCP)5 connects Claude to external tools.

.mcp.json Configuration

{
  "mcpServers": {
    "slack": {
      "type": "http",
      "url": "https://mcp.slack.com/mcp"
    },
    "playwright": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@playwright/mcp@0.0.80"]
    }
  }
}

Every entry MUST declare a type. Claude Code reads an entry that has a url but no type as a stdio server, skips it, and reports the misconfiguration.6

Slack

Slack serves MCP over JSON-RPC 2.0 on Streamable HTTP at https://mcp.slack.com/mcp. SSE connections and Dynamic Client Registration are not supported.7 A bare URL is not an authenticated configuration:

  • Registered app. Every MCP client MUST be backed by a registered Slack app with a fixed app ID, and only Marketplace-published or internal apps may use MCP.7 Claude Code is one of Slack’s listed partner clients, so a team MAY connect through it without building an app of its own.
  • Admin approval. Workspace admins approve MCP clients through the standard Slack app approval process, and an app’s allowed IP ranges apply to MCP traffic too.7
  • Confidential OAuth. A client of your own authenticates with its client_id and client_secret against https://slack.com/oauth/v2_user/authorize and https://slack.com/api/oauth.v2.user.access, or discovers them from https://mcp.slack.com/.well-known/oauth-authorization-server. Desktop clients SHOULD use PKCE.7
  • Least-privilege scopes. Grant only the user-token scopes the tools you actually call: search:read.public to search public messages, channels:history to read a channel, chat:write to post.7 A read-only workflow MUST NOT request write scopes.
claude mcp add --transport http slack https://mcp.slack.com/mcp

Then run /mcp inside the session: it runs the OAuth flow for remote servers and shows each server’s connection state.6

Common MCP Servers

ServerPurposeType
PlaywrightBrowser automation, screenshotsstdio
SlackSearch and post messageshttp
SentryError logs and debugginghttp
PostgreSQLDatabase queriesstdio
GitHubIssues, PRs, actionshttp

MCP for Visual Iteration

SHOULD use Claude Code’s built-in Chrome integration4 for screenshots and browser checks, because it needs no MCP server and drives the browser you are already signed into:

claude --chrome

Prerequisites MUST be met before relying on it4:

  • Chrome, Edge, or another Chromium browser, plus the Claude in Chrome extension 1.0.36 or later
  • Sign-in with /login on a direct Anthropic plan (Pro, Max, Team, or Enterprise) — API-key and claude setup-token sessions keep the integration off
  • Not available under Windows Subsystem for Linux

Where those prerequisites do not hold, SHOULD use Microsoft’s @playwright/mcp, pinned to an exact version6:

{
  "mcpServers": {
    "playwright": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@playwright/mcp@0.0.80", "--isolated", "--headless"]
    }
  }
}

Why

@anthropic/mcp-puppeteer does not exist on the public registry, and @modelcontextprotocol/server-puppeteer is marked deprecated (“Package no longer supported”)7, so both fail before browser automation starts. @playwright/mcp 0.0.80 is the current release6 and requires Node.js 18 or newer.

Pin the version: @latest re-resolves on every launch, so an unpinned npx line runs whatever was published since the config was reviewed. --isolated keeps the browser profile in memory, so a session cannot inherit or persist cookies from a previous run, and lets several clients share one workspace without fighting over the persistent profile6.

The server drives a real browser with network access. Treat any page it visits as untrusted input: point it at your own development server, and MUST NOT give it credentials that matter more than the task.

Then in conversation:

Take a screenshot of http://localhost:3000/login

Why the browser package is pinned

Prerequisite. @playwright/mcp 0.0.80 requires Node.js 18 or newer.[^8]

Pin the version. @playwright/mcp@latest re-resolves on every start, so a release can change agent behavior between two runs of the same task. Pin the version and bump it deliberately. --isolated keeps the browser profile in memory, and --headless overrides the headed default.[^8]

MCP or CLI. Microsoft notes that CLI invocations exposed as Skills are more token-efficient for coding agents, because they avoid loading large tool schemas and accessibility trees into context, and keeps MCP for work that benefits from persistent browser state and iterative reasoning over page structure, such as exploratory automation and self-healing tests.[^8] Visual iteration is that second case, so this guide configures MCP; a team whose browser work is scripted SHOULD compare the pinned Playwright CLI Skill.


Long-Running Sessions

Claude Code can run for hours or days with proper configuration.

Permission Modes

ModeFlagUse Case
Normal(default)Interactive development
Don’t Ask--permission-mode=dontAskLong tasks, respects denylist
Skip All--dangerously-skip-permissionsSandboxed environments only

Stop Hooks for Auto-Resume

Stop hooks verify completion and auto-resume when criteria are not met. Use the same enforcing gate as Stop: Verify Completion — a plain npm test && npm run lint command exits 1 on failure, which Claude Code treats as non-blocking:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/verify-complete.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

Auto-resume is bounded: Claude Code ends the turn after eight consecutive blocks2. A long-running session MUST NOT be planned on the assumption that the hook resumes indefinitely.

The ralph-wiggum Plugin

Keeps prompting Claude until real completion:

claude plugins install ralph-wiggum

Long-Running Session Checklist

  • Permissions allowlist configured
  • PostToolUse hooks for auto-format/lint
  • Stop hooks for verification
  • Clear completion criteria defined
  • Running in sandbox/container (if using skip-permissions)

Syncing from crefft

Manual installs and automated syncs MUST call scripts/sync-claude-config.sh from a checkout at a pinned release tag. The script requires Bash, Git, and Python 3. It reads agents/ and commands/ at the repository root, plus configs/claude/skills/, configs/claude/infrastructure/, and configs/claude/settings.json. A missing component is an error before any destination write.

Why

One applier gives manual installs and workflows the same source layout, ownership checks, and settings policy. Copying directories independently can delete project files or silently omit part of a release.

Manual Sync

Run from the consuming project’s root:

CREFFT_REF=v2026.10.1
git clone --branch "${CREFFT_REF}" --depth 1 \
  https://github.com/agh/crefft.git "${HOME}/.crefft"
"${HOME}/.crefft/scripts/sync-claude-config.sh" --ref "${CREFFT_REF}" "${PWD}"

For an existing clone, fetch the chosen release, check it out, and call the same script:

CREFFT_REF=v2026.10.1
git -C "${HOME}/.crefft" fetch --depth 1 origin \
  "refs/tags/${CREFFT_REF}:refs/tags/${CREFFT_REF}"
git -C "${HOME}/.crefft" checkout --detach "${CREFFT_REF}"
"${HOME}/.crefft/scripts/sync-claude-config.sh" --ref "${CREFFT_REF}" "${PWD}"

Do: keep the checkout at the selected release and invoke its script. Don’t: copy or mirror configs/claude/ into the project; agents and commands no longer live there, and whole-directory deletion has no ownership boundary.

Ownership and Settings

The script writes .claude/crefft-receipt.json, recording the release tag, resolved commit SHA, and SHA-256 of every installed file. Consumers MUST commit the receipt alongside the installed files.

Later syncs replace or remove only receipt-owned files. Project-specific agents, commands, and skills beside those files survive. A conflicting unowned path, a symlink, or a locally modified owned file stops the entire sync with a conflict list before any write. The script never deletes whole component directories. Repeating the same release changes neither the files nor the receipt.

For an earlier manual installation without a receipt, consumers MAY pass --adopt to claim existing files whose contents are identical to the release. Different contents still conflict; file modes and timestamps do not establish identity. Review and relocate conflicting files rather than deleting them blindly.

Existing .claude/settings.json is kept, even on upgrades. It is installed only when absent unless --overwrite-settings explicitly permits replacing it. An existing unowned settings file is not claimed by default.

Why

The receipt is the deletion boundary, not the containing directory. Retaining it makes an upstream removal safe without removing a project’s own configuration. Settings can contain project-specific permissions and hooks, so replacing them requires a separate opt-in.

GitHub Action for Auto-Sync

Use the maintained sync-crefft.yml template, not an embedded copying workflow:

mkdir -p .github/workflows
curl -fsSL -o .github/workflows/sync-crefft.yml \
  https://raw.githubusercontent.com/agh/crefft/v2026.10.1/configs/github/workflows/sync-crefft.yml

Follow the adoption notes for repository permissions. The template checks out CREFFT_REF (v2026.10.1 by default) into .crefft-upstream/ and calls .crefft-upstream/scripts/sync-claude-config.sh. Set CREFFT_COMMIT to a reviewed full commit SHA for additional verification. Every action remains pinned by full SHA.

The workflow opens a pull request containing only .claude/ changes, including the receipt. A manual dry run invokes the same applier in a temporary project clone and reports changes without altering the project’s workspace or opening a pull request.

Version Pinning

Consumers MUST select a release tag, not main. The script verifies that HEAD resolves to that tag, that VERSION matches it, and that source inputs are clean. It records both the tag and resolved commit in the receipt. Tarball-only installs are not supported because they lack the Git metadata needed to verify the checkout.

Consumers SHOULD also supply --commit with a full SHA obtained through their release-review process; the workflow exposes the same check as CREFFT_COMMIT. A mismatch fails before destination writes. Passing the checkout’s own SHA without independently reviewing it adds no protection.

Why

A release pin makes updates deliberate. A separately reviewed commit pin also detects a moved tag. Checking the source tree prevents an edited checkout from claiming the identity of an unchanged release.


See Also


References

Footnotes

  1. Claude Code Documentation — Official CLI documentation ↩

  2. Hooks reference — Exit codes, stop_hook_active, the eight-block continuation cap, exec form vs shell form ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  3. Keep Claude working toward a goal — Session-scoped completion conditions with /goal ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  4. Use Claude Code with Chrome — Built-in browser integration and its prerequisites ↩ ↩2 ↩3

  5. MCP Documentation — Model Context Protocol specification ↩

  6. @playwright/mcp registry metadata — Version 0.0.80, published 2026-09-01; Node.js 18+ ↩ ↩2 ↩3 ↩4 ↩5

  7. @modelcontextprotocol/server-puppeteer registry metadata — deprecated: "Package no longer supported" ↩ ↩2 ↩3 ↩4 ↩5 ↩6