# Run BrassCoders Automatically in a Claude Code Hook

A Claude Code hook is a shell command that Claude Code runs for you at a set moment: after an edit, before a Bash call, when a session opens. Point one at `brasscoders scan` and the deterministic scan runs on its own. It writes `.brass/ai_instructions.yaml` to the project, and Claude Code reads that file as context. You stop pasting scanner output into the chat, and you stop remembering to run the scanner at all.

Two existing guides cover [what Claude Code does with the YAML](https://coppersun.dev/blog/how-claude-code-reads-brasscoders-findings/) and [how the same file drives a Cursor session](https://coppersun.dev/blog/brasscoders-cursor-workflow/). This one is the wiring: the exact `.claude/settings.json` entry that fires the scan at the right moment, so the findings are already current when the assistant starts reasoning about your code.

## What a Claude Code Hook Actually Runs

BrassCoders plugs into Claude Code as an ordinary command — `brasscoders --offline scan .` — that Claude Code runs at a lifecycle event defined in `.claude/settings.json`, not as a plugin or an API. A hook is three nested levels: the event name, a matcher, and the command to run.

Claude Code fires hooks at events like PostToolUse (after a tool runs), SessionStart (when a session opens), and UserPromptSubmit (when you send a message). For tool events, the matcher filters by tool name, so a matcher of `Edit|Write` runs only after the assistant edits or creates a file. Hooks live in `.claude/settings.json` for one project or `~/.claude/settings.json` for every project you open, and each command hook is a `{ "type": "command", "command": "..." }` entry nested under its event and matcher. The full event list and field reference live in the [Claude Code hooks documentation](https://code.claude.com/docs/en/hooks).

## The Config: Scan After Every Edit

BrassCoders refreshes its findings after each change when you register a PostToolUse hook matching `Edit|Write`, so `.brass/ai_instructions.yaml` reflects the files the assistant just touched. The whole setup is one block in `.claude/settings.json`.

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "cd \"${CLAUDE_PROJECT_DIR}\" && brasscoders --offline scan . --incremental"
          }
        ]
      }
    ]
  }
}
```

Save it, and Claude Code activates the hook for the current project. After the assistant edits a file, the scan runs, and the YAML on disk is current before the next turn. The `${CLAUDE_PROJECT_DIR}` placeholder resolves to your project root, so the scan targets the right directory no matter where Claude Code spawned the command. The `--offline` flag holds the scan to zero network calls, and `--incremental` keeps each per-edit run cheap, which the next sections address.

## How the Findings Reach the Assistant

BrassCoders hands its output to Claude Code through `.brass/ai_instructions.yaml`, a severity-sorted YAML file the assistant reads as project context with no paste step. A SessionStart hook can go one step further and print a one-line pointer whose text Claude Code drops straight into the session.

Claude Code surfaces a hook's plain-text stdout as context the model can act on for a few events, SessionStart among them. Run the scan once when the session opens and echo where the results landed:

```json
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "cd \"${CLAUDE_PROJECT_DIR}\" && brasscoders --offline scan . && echo 'BrassCoders wrote .brass/ai_instructions.yaml. Read it before editing.'"
          }
        ]
      }
    ]
  }
}
```

The echoed line arrives as context the assistant sees at the top of the session, so it knows the findings file exists and opens it before it changes any code. What the assistant opens is a severity-ordered list, one entry per finding:

```yaml
issues:
  - id: bandit-B608-001
    severity: critical
    file_path: api/views.py
    line_number: 47
    title: SQL Injection via String Formatting
    remediation: Replace with a parameterized query using placeholders
      and a separate values tuple.
```

Claude Code reads the critical entries first, opens the flagged line, and writes the fix; the hook's only job is to guarantee that list is current. What each field does — severity ordering, line anchors, remediation notes — is the subject of the [findings-file walkthrough](https://coppersun.dev/blog/how-claude-code-reads-brasscoders-findings/).

## Scan Right Before a Commit

BrassCoders can refresh its findings in the moment before a commit when you attach a PreToolUse hook to the Bash tool and narrow it with the `if` field to git commit commands. The scan runs, the YAML updates, and the assistant has the current findings in front of it before the commit lands.

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(git commit *)",
            "command": "cd \"${CLAUDE_PROJECT_DIR}\" && brasscoders --offline scan . --incremental"
          }
        ]
      }
    ]
  }
}
```

The `if` field uses Claude Code's permission-rule syntax to scope the hook to a subset of Bash calls; the [hooks documentation](https://code.claude.com/docs/en/hooks) covers the exact matching rules. This is the assistant's local, in-session checkpoint, not a build gate. For a hard gate that blocks a bad commit on the shared branch, a CI step on push and a git pre-commit hook are the right tools, and the [every-commit guide](https://coppersun.dev/run-on-every-commit/) has both.

## Keep the Per-Edit Scan Fast

BrassCoders's `--incremental` flag re-scans only the files that changed since the last scan for its file-local scanners and replays cached findings for everything unchanged, so a per-edit hook doesn't pay for a full scan each time. The cross-file scanners, Pysa and ast-grep among them, still run in full for correctness.

On the first run, or when the cache is missing, `--incremental` falls back to a full scan to seed itself. After that, editing one file re-runs the file-local scanners on that file alone and reuses cached results for the rest. If you'd rather not block the edit loop, set the command hook's `async` field to true so the scan runs in the background, or drop the per-edit hook and scan once at SessionStart. A one-shot session scan carries no per-edit cost.

## Scope the Scan With .brassignore

BrassCoders reads a `.brassignore` file at the project root and skips the paths listed in it, which keeps a per-edit hook from re-flagging test fixtures or vendored directories every time the assistant saves a file. A focused scan means the assistant reads a shorter, more relevant findings list.

A scan the assistant triggers on every edit is only as useful as it is focused. Point `.brassignore` at the directories you don't want in the findings — generated migrations, `node_modules`, a `tests/fixtures/` folder full of placeholder credentials — and those paths drop out of every scan the hook runs. The [.brassignore tuning guide](https://coppersun.dev/blog/tuning-brasscoders-brassignore/) covers the file format and how it differs from `.gitignore`.

## A Hook Triggers the Scan; It Doesn't Watch

BrassCoders has no daemon and no file watcher; the `watch` command was removed in version 2.0.9, so a Claude Code hook leaves nothing running between scans. Each time the hook fires, `brasscoders scan` runs once, writes its YAML, and exits.

The hook is the trigger; the cadence is a choice you make by picking the event. A Claude Code session is one place to run the scanner. Your CI on push and a git pre-commit hook are two others; the [every-commit guide](https://coppersun.dev/run-on-every-commit/) has that configuration. Those gates don't replace the Claude Code hook, which keeps your local assistant's context current as you work.

Install the scanner:

```bash
pip install brasscoders
```

Add the PostToolUse or SessionStart block to `.claude/settings.json`, open Claude Code in the project, and the findings are waiting in `.brass/ai_instructions.yaml` before you ask for a single change. Apache 2.0, no account, `--offline` by choice.
