Run BrassCoders Automatically in a Claude Code Hook

Wire a Claude Code hook to run brasscoders scan on every edit, so .brass/ai_instructions.yaml stays fresh and the assistant reads findings without copy-paste.

Copper Sun Brass Team · · 7 min read
ai-code-reviewworkflowoss-core

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 and how the same file drives a Cursor session. 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.

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.

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

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

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.

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.

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

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.

Frequently Asked Questions

How do I run BrassCoders automatically in Claude Code?

BrassCoders runs automatically once you add a hook to `.claude/settings.json` that calls `brasscoders --offline scan .` at a lifecycle event. A PostToolUse hook with a matcher of `Edit|Write` runs the scan after each edit; a SessionStart hook runs it once when the session opens. Claude Code executes the command, and BrassCoders writes `.brass/ai_instructions.yaml`. Nothing runs in the background — the hook invokes the CLI on demand.

Can my AI assistant read BrassCoders findings without copy-paste?

BrassCoders writes findings to `.brass/ai_instructions.yaml` in the project directory, and Claude Code reads that file as project context, so you never paste scanner output into the chat. A SessionStart hook can also print a one-line pointer on stdout, which Claude Code adds to the session as text the assistant sees directly.

Which Claude Code hook event should run the BrassCoders scan?

BrassCoders fits two events cleanly: SessionStart runs the scan once when the session opens, and PostToolUse with a matcher of `Edit|Write` refreshes findings after each edit. SessionStart adds the least friction; PostToolUse keeps `.brass/ai_instructions.yaml` current as the assistant changes code. Both call the same `brasscoders --offline scan .` command.

Does a Claude Code hook turn BrassCoders into a background watcher?

BrassCoders has no watch mode or daemon — the `watch` command was removed in version 2.0.9 — so a Claude Code hook doesn't turn it into a background watcher. The hook makes Claude Code invoke `brasscoders scan` at a specific event; the scanner runs once per invocation and exits, with nothing resident in memory between scans.

Won't scanning on every edit slow down my Claude Code session?

BrassCoders's `--incremental` flag re-scans only the files that changed since the last scan for its file-local scanners and reuses cached findings for the rest. Pair a PostToolUse hook with `brasscoders --offline scan . --incremental`, or set the command hook's `async` field to true so the scan runs in the background without blocking the edit. A once-per-session SessionStart hook avoids the per-edit cost entirely.

Does the Claude Code hook send my code anywhere?

BrassCoders's OSS core makes zero outbound network calls, and the `--offline` flag enforces that at the CLI level, so a hook running `brasscoders --offline scan .` keeps every byte on the machine. BrassCoders Paid sends already-redacted findings plus a project signature under 7,500 characters, never raw source code.