# Claude Code CLI Configuration

> [crefft](/index.md) > [AI](/guides/ai.md) > 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](https://datatracker.ietf.org/doc/html/rfc2119).

## Overview

This guide covers Claude Code CLI[^1] configuration: hooks, permissions,
subagents, commands, and MCP. For universal AI workflows (Hero Flow, TDD,
Visual Iteration), see [AI Workflows](/guides/ai/ai-workflows.md).

## Quick Reference

| Task          | Command/File            |
| ------------- | ----------------------- |
| Start session | `claude`                |
| Plan mode     | `/plan`                 |
| Headless mode | `claude -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](#configuration-files)
2. [Hooks](#hooks)
3. [Permissions](#permissions)
4. [Subagents](#subagents)
5. [Custom Commands](#custom-commands)
6. [MCP Integration](#mcp-integration)
7. [Long-Running Sessions](#long-running-sessions)
8. [Syncing from crefft](#syncing-from-crefft)

***

## Configuration Files

Claude Code uses several configuration files:

```text
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

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

See [configs/claude/settings.json](/raw/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

| Hook          | When it Runs              | Use Case                       |
| ------------- | ------------------------- | ------------------------------ |
| `PostToolUse` | After Claude uses a tool  | Auto-format, auto-lint         |
| `PreToolUse`  | Before Claude uses a tool | Validation, logging            |
| `Stop`        | When Claude stops         | Verify 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 proceed[^3].

| Exit code                   | Effect on `PostToolUse`                                          | Effect on `Stop`                                       |
| --------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------ |
| `0`                         | Success. stderr goes to the debug log only; Claude never sees it | Claude stops                                           |
| `1` (or any other non-zero) | Non-blocking error, stderr not shown to Claude                   | Claude stops anyway                                    |
| `2`                         | Tool already ran, but Claude sees stderr                         | Blocks 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:

```bash
# 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:

```json
{
  "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:

```bash
#!/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}`[^3].
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:

```json
{
  "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:

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

```bash
#!/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 regardless[^3]. 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 stderr[^3]:

```json
{
  "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
disabled[^3].

For a completion condition that applies to one session rather than every
session in the project, **SHOULD** use `/goal`[^4] 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 field              | Value                                            | Event                       |
| ----------------------- | ------------------------------------------------ | --------------------------- |
| `.tool_input.file_path` | Path to file being written/edited                | `PostToolUse`               |
| `.tool_name`            | Name of tool being used                          | `PreToolUse`, `PostToolUse` |
| `.stop_hook_active`     | `true` when a stop hook already resumed the turn | `Stop`                      |

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

***

## Permissions

**MUST** configure permissions to avoid prompt fatigue without sacrificing safety.

### Permission Syntax

```text
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.[^4]

### 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:

```json
{
  "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.[^4] 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,[^4] 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.[^4] 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:

```json
{
  "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.[^4]

### crefft's Standard Denylist

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

```json
{
  "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.[^4] Bare
filenames follow gitignore semantics: `Read(.env)` and `Read(**/.env)` are
equivalent and match at any depth under the current directory.[^4]

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.[^5]

### Managing Permissions

```bash
# 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:

| Family       | Command     | Coordinator               | Purpose               |
| ------------ | ----------- | ------------------------- | --------------------- |
| **Code**     | `/code`     | Code Architect (Opus)     | Code quality review   |
| **System**   | `/system`   | System Architect (Opus)   | Infrastructure review |
| **Security** | `/security` | Security Architect (Opus) | Security analysis     |

See the family guides for full details:

* [Code Agent Family](/guides/ai/code-agents.md)
* [System Agent Family](/guides/ai/system-agents.md)
* [Security Agent Family](/guides/ai/security-agents.md)

### Code Agent Family (`/code`)

| Agent          | File                    | Purpose                   |
| -------------- | ----------------------- | ------------------------- |
| Code Architect | `code/architect.md`     | Opus coordinator          |
| Code Reviewer  | `code/reviewer.md`      | General code review       |
| Performance    | `code/performance.md`   | Deep performance analysis |
| Accessibility  | `code/accessibility.md` | WCAG/A11y compliance      |
| REST API       | `code/api-rest.md`      | REST API design           |
| GraphQL API    | `code/api-graphql.md`   | GraphQL schema design     |
| Test Writer    | `code/test-writer.md`   | Test generation           |
| Simplifier     | `code/simplifier.md`    | Complexity reduction      |

### System Agent Family (`/system`)

| Agent            | File                  | Purpose            |
| ---------------- | --------------------- | ------------------ |
| System Architect | `system/architect.md` | Opus coordinator   |
| Docker           | `system/docker.md`    | Container security |
| Ansible          | `system/ansible.md`   | Playbook review    |
| Linux            | `system/linux.md`     | OS hardening       |
| Verify           | `system/verify.md`    | CI/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

```bash
# 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/`:

```markdown
# .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

```bash
/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

| Command     | Purpose                    | Subcommands                                                           |
| ----------- | -------------------------- | --------------------------------------------------------------------- |
| `/code`     | Code quality review        | `quick`, `review`, `perf`, `a11y`, `api`, `tests`, `simplify`, `docs` |
| `/system`   | Infrastructure review      | `docker`, `ansible`, `linux`, `verify`                                |
| `/security` | Security analysis          | See [Security Agent Family](/guides/ai/security-agents.md)            |
| `/refactor` | Refactor code              | `--explain`, `--metrics`, `--incremental`, `--safe`                   |
| `/explain`  | Explain code               | `--deep`                                                              |
| `/plan`     | Create implementation plan | Feature description                                                   |

See [commands/](https://github.com/agh/crefft/tree/703c8156f7604b464921c851535242202541cfb3/commands) for full definitions.

***

## MCP Integration

Model Context Protocol (MCP)[^2] connects Claude to external tools.

### .mcp.json Configuration

```json
{
  "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.

```bash
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

| Server     | Purpose                         | Type  |
| ---------- | ------------------------------- | ----- |
| Playwright | Browser automation, screenshots | stdio |
| Slack      | Search and post messages        | http  |
| Sentry     | Error logs and debugging        | http  |
| PostgreSQL | Database queries                | stdio |
| GitHub     | Issues, PRs, actions            | http  |

### MCP for Visual Iteration

**SHOULD** use Claude Code's built-in Chrome integration[^5] for screenshots
and browser checks, because it needs no MCP server and drives the browser you
are already signed into:

```bash
claude --chrome
```

Prerequisites **MUST** be met before relying on it[^5]:

* 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 version[^6]:

```json
{
  "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 release[^6] 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 profile[^6].

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:

```text
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

| Mode      | Flag                             | Use Case                      |
| --------- | -------------------------------- | ----------------------------- |
| Normal    | (default)                        | Interactive development       |
| Don't Ask | `--permission-mode=dontAsk`      | Long tasks, respects denylist |
| Skip All  | `--dangerously-skip-permissions` | Sandboxed 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](#stop-verify-completion)
— a plain `npm test && npm run lint` command exits 1 on failure, which Claude
Code treats as non-blocking:

```json
{
  "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
blocks[^3]. 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:

```bash
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)

***

<a id="syncing-from-doctrine"></a>

## Syncing from crefft

Manual installs and automated syncs **MUST** call
[`scripts/sync-claude-config.sh`](https://github.com/agh/crefft/blob/703c8156f7604b464921c851535242202541cfb3/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:

```bash
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:

```bash
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](/raw/configs/github/workflows/sync-crefft.yml),
not an embedded copying workflow:

```bash
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](/configs/github.md#adopting-sync-crefftyml)
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

* [Code Agent Family](/guides/ai/code-agents.md) — Code quality review agents
* [System Agent Family](/guides/ai/system-agents.md) — Infrastructure review agents
* [Security Agent Family](/guides/ai/security-agents.md) — Security analysis agents
* [AI Workflows](/guides/ai/ai-workflows.md) — Hero Flow, TDD, Visual Iteration
* [AGENTS.md Patterns](/guides/ai/agents-md.md) — Project context files
* [Claude Best Practices](/guides/ai/claude.md) — Model selection, API usage

***

## References

[^1]: [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code) — Official CLI documentation

[^2]: [MCP Documentation](https://modelcontextprotocol.io/) — Model Context Protocol specification

[^3]: [Hooks reference](https://code.claude.com/docs/en/hooks) — Exit codes, `stop_hook_active`, the eight-block continuation cap, exec form vs shell form

[^4]: [Keep Claude working toward a goal](https://code.claude.com/docs/en/goal) — Session-scoped completion conditions with `/goal`

[^5]: [Use Claude Code with Chrome](https://code.claude.com/docs/en/chrome) — Built-in browser integration and its prerequisites

[^6]: [`@playwright/mcp` registry metadata](https://registry.npmjs.org/@playwright/mcp/latest) — Version 0.0.80, published 2026-09-01; Node.js 18+

[^7]: [`@modelcontextprotocol/server-puppeteer` registry metadata](https://registry.npmjs.org/@modelcontextprotocol/server-puppeteer/latest) — `deprecated: "Package no longer supported"`

---

Source version: 2026.10.4. [Source file](https://github.com/agh/crefft/blob/703c8156f7604b464921c851535242202541cfb3/guides/ai/claude-code.md).
