Running BrassCoders on Every Commit

Installing BrassCoders doesn't make it watch your commits — it's a command-line scanner you invoke, not a background service. This guide covers the two ways to get a scan on every commit: a CI step on push, and a git pre-commit hook. It also explains why there's no auto-run mode, and how the exit code turns a scan into a gate.

A common expectation is that adding BrassCoders to a project means it starts running on its own. It doesn't. BrassCoders is a single-invocation command-line tool: it runs when something runs it, writes .brass/*.yaml, and exits. Getting a scan on every commit is a deliberate setup step — and there are exactly two places to wire it in.

This guide sets up both. Both use the same command, brasscoders --offline scan, which runs the 12-scanner pass (Bandit, Pylint, Pyre/Pysa, Semgrep, ast-grep, detect-secrets, plus six custom detectors), makes zero outbound network calls, and exits non-zero on CRITICAL findings. For the broader gating strategy — pre-commit plus CI plus a branch-protection merge gate with an audit trail — see the CI/CD Code Gates guide. This page is the focused setup for the on-every-commit cadence itself.

Why It Isn't Automatic

BrassCoders is a command you invoke — it runs when a command runs it, and nothing else, because it has no background daemon or file watcher. BrassCoders removed its watch command and its monitoring module in version 2.0.9: a resident watcher added a second, degraded code path without earning its keep. A single-invocation CLI is predictable — same command, same input, same output, then it's gone. So "on every commit" is never automatic; it's whatever cadence you wire up.

That design is why the two mechanisms below exist. Neither is a BrassCoders feature you toggle — they're standard git and CI hook points that run any command on a trigger. BrassCoders is just the command they run.

Mechanism 1: A CI Step on Push

A CI step running BrassCoders on every push is the enforced, central way to scan every commit — it runs on the server, on every contributor's push, and no developer can skip it. The scan exits non-zero on CRITICAL findings, which fails the job and blocks the merge when paired with branch protection.

GitHub Actions:

name: BrassCoders Scan
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  brasscoders:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Install BrassCoders
        run: pip install brasscoders
      - name: Run BrassCoders scan
        run: brasscoders --offline scan .
      - name: Upload .brass artifact
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: brasscoders-findings
          path: .brass/
          retention-days: 90

The if: always() uploads the .brass/ directory even when the scan fails, so reviewers can see what was found — not just that something was. Pin a version (pip install brasscoders==2.0.12) when you need reproducible CI across an audit period.

GitLab CI is the same shape:

brasscoders:
  image: python:3.12
  script:
    - pip install brasscoders
    - brasscoders --offline scan .
  artifacts:
    when: always
    paths:
      - .brass/
    expire_in: 90 days

Mechanism 2: A Git Pre-Commit Hook

A git pre-commit hook runs BrassCoders on the developer's machine before the commit is created, so a CRITICAL finding blocks the commit and bad code never enters git history. It's the fastest feedback loop — seconds, local, no network — and it catches issues at the lowest cost, before anything reaches the repository.

Using the pre-commit framework, add BrassCoders as a local hook:

# pip install pre-commit brasscoders

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: brasscoders
        name: BrassCoders scan
        entry: brasscoders --offline scan
        language: system
        pass_filenames: false
        stages: [pre-commit]

# then install the hook
pre-commit install

pass_filenames: false runs BrassCoders against the full project directory rather than individual staged files — necessary because BrassCoders does project-level analysis (taint flows, cross-file secret patterns, import resolution) that needs the whole tree. Pre-commit hooks can be bypassed with git commit --no-verify, which is why the CI step above is the enforced backstop for anything that skips the hook.

Which One to Pick

Most teams run both: the pre-commit hook catches issues cheapest — nothing is in the repository yet — and the CI step is the enforced backstop that can't be bypassed with git commit --no-verify. If you run only one, run CI: it's the gate no developer can skip, it runs on external contributors' code too, and its output becomes the audit record.

How the Exit Code Gates the Build

BrassCoders gates a commit through its exit code: it exits 1 when it finds any CRITICAL issue and 0 when it doesn't, and that exit code is what a CI job or a pre-commit hook reads to fail the build or block the commit. Non-critical findings (HIGH, MEDIUM, LOW, INFO) are written to .brass/ for review but don't change the exit code.

That's the whole contract. Everything else — which findings to fix, which are false positives — happens after the gate, when a developer hands .brass/ai_instructions.yaml to Claude Code or Cursor for triage. The gate's only job is to stop a CRITICAL finding from shipping silently.

Frequently Asked Questions

Does BrassCoders run automatically on every commit?

No. BrassCoders is a command-line scanner you invoke with brasscoders scan. It has no background daemon or file watcher, so it runs only when a CI step or a git hook runs it. Scanning on every commit becomes true once you wire BrassCoders into CI on push or a git pre-commit hook.

Does BrassCoders have a watch or daemon mode?

No. BrassCoders removed its watch command and monitoring module in version 2.0.9. BrassCoders is a single-invocation CLI by design: it scans, writes YAML to .brass/, and exits. Continuous background scanning is not a feature it offers — you get on-every-commit cadence from CI or a pre-commit hook instead.

Should I use CI, a pre-commit hook, or both?

Both, if you can. The pre-commit hook is the fast local catch — it runs on the developer machine before git commit completes, so bad code never enters history. CI is the enforced backstop — it runs on every push on the server and can't be skipped with git commit --no-verify. If you pick one, pick CI: it's the gate no developer can bypass.

What makes the build or commit fail?

BrassCoders exits with code 1 when it finds any CRITICAL issue and code 0 when it doesn't. A CI job or a pre-commit hook reads that exit code to fail the build or block the commit. Non-critical findings (HIGH, MEDIUM, LOW, INFO) are written to .brass/ for review but do not affect the exit code.

Can the pre-commit hook run without network access?

Yes. brasscoders --offline scan makes zero outbound network calls and refuses any attempt to make one, which is what you want in a git hook: no timeouts, no network dependency, no accidental data egress. The OSS core is offline by default; the flag makes the guarantee explicit.