Claude Code hooks
Stop Claude Code from committing or pushing code that fails fallow audit. A PreToolUse hook blocks the command and sends the findings back to the agent to fix.
This gate stops Claude Code from committing or pushing code that fails fallow audit. Claude gets the findings, fixes the code, and tries again.
The gate is not a Git hook. It is a local PreToolUse hook on the Claude Bash tool. When Claude runs git commit or git push, the hook runs fallow audit --format json --quiet --explain --gate-marker agent:
- On
passorwarn, the command continues. - On
fail, the hook blocks the command and sends the JSON findings to Claude on stderr.
This is Option C (local agent gate) in the adoption rollout. Use it together with the shared CI gate from fallow audit. It does not replace the CI gate. See Adopt Fallow in an existing repo.
Install it in one command
fallow hooks install --target agent
The command writes a project-level .claude/settings.json and .claude/hooks/fallow-gate.sh. If your repo also has an AGENTS.md or .codex/, it adds a managed Codex fallback block to AGENTS.md. The block includes the task-to-command matrix from fallow schema. When you upgrade fallow and run the installer again, the block updates. To remove the generated files later, run fallow hooks uninstall --target agent.
To preview the changes, add --dry-run:
fallow hooks install --target agent --dry-run
The output has one line for each path and says what changes:
fallow hooks install --target agent (install) (dry run):
.claude/settings.json updated (1 handler added, 1 preserved)
.claude/hooks/fallow-gate.sh would create
AGENTS.md would insert managed block
fallow agent install installs this gate together with the skill, the MCP registration, and an AGENTS.md task map for each harness it detects. The older fallow setup-hooks command is deprecated. It still works in all of fallow 3 with a warning, and the next major version removes it.
Install globally for every repo
To install the gate once for all repos, pass --user. The installer writes to your home directory:
- The settings file goes to
~/.claude/settings.json. - The hook script goes to
~/.claude/hooks/fallow-gate.sh. - The handler command uses
"$HOME"in place of"$CLAUDE_PROJECT_DIR", so it works in every repo that Claude Code opens.
fallow hooks install --target agent --user
When a repo has both, the project-level install overrides the global one. Use this to give one repo a stricter or looser policy.
Gate semantics
The hook blocks the git commit or git push only when fallow audit returns verdict: "fail". When local Impact tracking is enabled, the --gate-marker agent flag lets it record blocked-then-cleared containment events.
Audit defaults to gate=new-only. Only findings that the changeset introduces fail the gate. Inherited findings in touched files still show in attribution, and each of those issues has introduced: false. They do not change the verdict.
This default helps you adopt fallow on a legacy codebase. An agent that edits a 10-year-old file is not blocked by the old unused exports in it. Only new ones block it.
To gate every finding in changed files (strict mode), set the gate in your project config:
[audit]
gate = "all"
You can also edit .claude/hooks/fallow-gate.sh to pass --gate all. But each fallow hooks install --target agent run regenerates the script and removes your edit. Use the config file for a permanent setting.
Comparison base
The hook runs fallow audit with no --base. Audit then uses the git merge-base (fork point) as the comparison base. It compares against the upstream of the branch, or else the remote default (origin/main). Audit finds the merge-base at run time, so a new branch from origin/main is gated only on its own changes.
Two setups need an explicit base. For these, set FALLOW_AUDIT_BASE. It takes effect because the gate passes no --base:
- Forks. On a fork,
originis your fork, and it can be behind the real upstream. SetFALLOW_AUDIT_BASE=upstream/mainto compare against the real upstream. - A non-default integration branch. If your team merges into
developor a release branch and not intomain, setFALLOW_AUDIT_BASE=origin/develop.
Use the env var, not a script edit. Each fallow hooks install --target agent run regenerates fallow-gate.sh, so a reinstall removes script edits.
Version floor
The gate requires a minimum fallow version, set by the FALLOW_GATE_MIN_VERSION env var (default 2.85.0). The gate passes --gate-marker agent, which v2.85.0 added. Older binaries reject this flag, and every audit would then fail open. So if the fallow on PATH is older than the floor, the gate blocks and shows an upgrade hint. This stops an old npm-global or ~/.cargo/bin/fallow from looking like a working gate.
# Raise the floor (require a newer fallow across your team)
export FALLOW_GATE_MIN_VERSION=3.0.0
# Disable (only do this if you know why)
export FALLOW_GATE_MIN_VERSION=
Each block logs the path and version of the binary that ran, for example fallow-gate: blocked by fallow 3.31.0 at /usr/local/bin/fallow. You can see which binary ran without more debugging. The header of the installed script also has # Installer version: X.Y.Z. Use it when the gate does not agree with your current fallow --version.
Inspect hook state
fallow hooks status shows what is installed and changes nothing. It checks three places:
- the Git pre-commit hook
- the Claude Code gate
- the Codex
AGENTS.mdmanaged block
Agents and scripts read the JSON form before they propose setup changes:
fallow hooks status --format json
{
"hooks": {
"git": { "installed": true, "managed_block_present": true, "user_edited": false, "path": ".git/hooks/pre-commit", "script_version": null, "min_version_floor": null },
"claude": { "installed": true, "managed_block_present": true, "user_edited": false, "path": ".claude/hooks/fallow-gate.sh", "script_version": "3.1.0", "min_version_floor": "2.85.0" },
"codex": { "installed": false, "managed_block_present": false, "user_edited": false, "path": "AGENTS.md", "script_version": null, "min_version_floor": null }
}
}
user_edited is true when a hook file exists but has lost the fallow generator marker, or when a managed block is broken. An agent then knows not to overwrite the file without asking. script_version and min_version_floor give the installer version and the version floor of the gate script. Tools use them to find an old gate.
Remove it
fallow hooks uninstall --target agent
This command does three things:
- It removes the fallow-gate handler from
.claude/settings.json. - It deletes the hook script if the script still has the generator marker. To also remove a user-edited script, pass
--force. - It removes the managed block from
AGENTS.md.
Handlers that you added outside fallow stay. To uninstall the global files at ~/.claude/ in place of the project files, pass --user.
Uninstall is idempotent. A second run prints unchanged or not present and exits 0. It also supports a dry run: fallow hooks uninstall --target agent --dry-run.
fallow hooks install --target agent also finds and removes hook commands from older manual setups. These include absolute paths, ~/-based paths, and Windows drive paths. When you run it on an existing install, it upgrades the handler to the canonical form and leaves no duplicates.
fallow hooks install --target git is a different hook. It creates a shell-level Git pre-commit hook in .git/hooks/ that runs fallow on changed files. That hook checks human commits. The gate on this page checks agent commits. You can use both together.
Manual setup
1. Create .claude/settings.json
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/fallow-gate.sh"
}
]
}
]
}
}
2. Create .claude/hooks/fallow-gate.sh
Copy the canonical script from the fallow repo, crates/cli/src/setup_hooks/fallow-gate.sh, into .claude/hooks/fallow-gate.sh. Leave out the # Installer version: line, because only the installer writes it. Use fallow hooks install --target agent when you can. Copy the script by hand only when you cannot run that command.
3. Make the script executable
chmod +x .claude/hooks/fallow-gate.sh
The hook looks for fallow in this order: fallow on PATH, ./node_modules/.bin/fallow, yarn exec fallow (for Yarn Plug'n'Play), and npx --no-install fallow. Keep jq on PATH, because the hook reads the stdin JSON from Claude and the JSON output from fallow. When the hook skips the check, it prints one line to stderr. The agent then knows that the gate did not run.
In JSON mode, fallow audit reports invalid refs, non-Git directories, and config errors as { "error": true, ... } with exit code 2. The gate does not block on these errors. A new repo or a repo with empty history can then commit before the first successful audit.
This gate runs before Claude runs the shell command, so git push --no-verify does not bypass it. Keep fallow audit in CI as the shared gate for humans and for workflows outside Claude.
Codex fallback
Codex has its own hooks, but fallow does not install a Codex hook. For Codex, fallow uses an AGENTS.md block. Add this block to the AGENTS.md at your repo root:
## Fallow local gate
Before any `git commit` or `git push`, run `fallow audit --format json --quiet --explain --gate-marker agent`. If the verdict is `fail`, fix the reported findings before retrying. Treat JSON runtime errors like `{ "error": true, ... }` as non-blocking.
fallow hooks install --target agent keeps this block up to date for you. The block is between <!-- fallow:setup-hooks:start --> and <!-- fallow:setup-hooks:end --> markers, so you can run the command again safely.
Why this works well with agents
fallow audit --explain gives Claude structured findings with docs links and fix guidance. Agent Skills teach the agent how to act on them. The loop is short:
- Claude tries to commit or push.
- The gate blocks only on
fail-severity issues. - Claude reads the JSON output and fixes the code.
- Claude tries again.