# Claude Code hooks

> Stop Claude Code and Codex 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.

> Page index of all docs: https://fallow.tools/llms.txt
> Docs MCP server (search and read the docs): https://fallow.tools/docs/mcp
> HTML version of this page: https://fallow.tools/docs/integrations/claude-hooks/

This gate stops Claude Code and Codex from committing or pushing code that fails `fallow audit`. The agent gets the findings, fixes the code, and tries again.

The gate is not a Git hook. It is a local `PreToolUse` hook on the `Bash` tool of the agent. When the agent runs `git commit` or `git push`, the hook runs `fallow audit --format json --quiet --explain --gate-marker agent`:

- On `pass` or `warn`, the command continues.
- On `fail`, the hook blocks the command and sends the JSON findings to the agent on stderr.

> **Info:** This is Option C (local agent gate) in the adoption rollout. Use it together with the shared CI gate from [fallow audit](https://fallow.tools/docs/cli/audit/). It does not replace the CI gate. See [Adopt Fallow in an existing repo](https://fallow.tools/docs/adoption/#7-turn-on-pr-enforcement).

## Install it in one command

```bash
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 a `.codex/` directory, the command also installs the [Codex gate](https://fallow.tools/docs/integrations/claude-hooks/#codex-gate). An `AGENTS.md` file alone does not select Codex, because Cursor and fallow also write that file. When no `.claude/` or `.codex/` directory exists, the command installs the Claude Code gate. To remove the generated files later, run `fallow hooks uninstall --target agent`.

To preview the changes, add `--dry-run`:

```bash
fallow hooks install --target agent --dry-run
```

The output has one line for each path and says what changes:

```text
fallow hooks install --target agent (install) (dry run):
  .claude/settings.json                       updated (1 handler added, 1 preserved)
  .claude/hooks/fallow-gate.sh                would create
```

With a `.codex/` directory, the output also has lines for `.codex/hooks.json`, `.codex/hooks/fallow-gate.sh`, and `AGENTS.md`.

> **Note:** [`fallow agent install`](https://fallow.tools/docs/cli/agent/) 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 runs `"$HOME"/.claude/hooks/fallow-gate.sh`, so it works in every repo that Claude Code opens.

For Codex, `--user` writes the gate to `~/.codex/hooks.json` and `~/.codex/hooks/fallow-gate.sh`. The user handler runs the audit from the git root of the session. With `--user`, fallow does not write the `AGENTS.md` block.

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

## Run from a subdirectory

The gate script and `fallow audit` read paths from the current directory. An agent session can work in a package directory below the install root. The project handler thus walks up from the session directory to the nearest directory that holds the gate script, and runs the audit there. The audit then covers the full project, and not only the package.

The walk stops at the first `.git` entry. A nested worktree thus does not use the gate of the checkout around it. When the walk finds no gate script:

- The Claude Code handler runs the script under `$CLAUDE_PROJECT_DIR`.
- The Codex handler exits 0 without output.

Run `fallow agent install` or `fallow hooks install --target agent` again to get the current handler.

## Gate semantics

The hook blocks the `git commit` or `git push` only when `fallow audit` returns `verdict: "fail"`. When local [Impact tracking](https://fallow.tools/docs/cli/impact/) 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:

**fallow.toml**

```toml
[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`](https://fallow.tools/docs/configuration/environment/#fallow_audit_base). It takes effect because the gate passes no `--base`:

- **Forks.** On a fork, `origin` is your fork, and it can be behind the real upstream. Set `FALLOW_AUDIT_BASE=upstream/main` to compare against the real upstream.
- **A non-default integration branch.** If your team merges into `develop` or a release branch and not into `main`, set `FALLOW_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.

```bash
# 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.33.1 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 four places:

- the Git pre-commit hook (`git`)
- the Claude Code gate (`claude`)
- the Codex `AGENTS.md` routing block (`codex`)
- the Codex gate in `.codex/hooks.json` (`codex_gate`)

Agents and scripts read the JSON form before they propose setup changes:

```bash
fallow hooks status --format json
```

```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 },
    "codex_gate": { "installed": false, "managed_block_present": false, "user_edited": false, "path": ".codex/hooks/fallow-gate.sh", "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

```bash
fallow hooks uninstall --target agent
```

This command does three things:

- It removes the fallow-gate handler from `.claude/settings.json` and `.codex/hooks.json`. When nothing else is left in `.codex/hooks.json`, it deletes that file.
- It deletes each hook script that 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/` and `~/.codex/` 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.

> **Note:** `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`

```json
{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "d=\"$(pwd)\"; until [ -f \"$d/.claude/hooks/fallow-gate.sh\" ] || [ -e \"$d/.git\" ] || [ \"$d\" = / ]; do d=\"$(dirname \"$d\")\"; done; if [ -f \"$d/.claude/hooks/fallow-gate.sh\" ]; then cd \"$d\" && exec ./.claude/hooks/fallow-gate.sh; fi; exec \"$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`](https://github.com/fallow-rs/fallow/blob/main/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

```bash
chmod +x .claude/hooks/fallow-gate.sh
```

> **Tip:** 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.

> **Note:** 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.

> **Warning:** 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 gate

Codex has its own [hooks](https://learn.chatgpt.com/docs/hooks). For Codex, the installer writes two files:

- `.codex/hooks.json` with a `PreToolUse` handler. The matcher is `^Bash$`.
- `.codex/hooks/fallow-gate.sh`, the same gate script that Claude Code uses.

The installer merges the handler into an existing `.codex/hooks.json` and keeps all other hooks. Codex runs a project hook only after you trust it in `/hooks`. Until then, the gate does not run.

The handler in `.codex/hooks.json`:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "^Bash$",
        "hooks": [
          {
            "type": "command",
            "command": "d=\"$(pwd)\"; until [ -f \"$d/.codex/hooks/fallow-gate.sh\" ] || [ -e \"$d/.git\" ] || [ \"$d\" = / ]; do d=\"$(dirname \"$d\")\"; done; if [ -f \"$d/.codex/hooks/fallow-gate.sh\" ]; then cd \"$d\" && exec ./.codex/hooks/fallow-gate.sh; fi; exit 0"
          }
        ]
      }
    ]
  }
}
```

The installer also writes a routing block to `AGENTS.md`. The block tells agents to use fallow, and names the hook as the enforcement layer. Agents without a hook run the check themselves. The block starts like this:

```md
## Fallow checks

Use fallow for code-quality, cleanup, and setup tasks in this repository. Fallow checks the changed code before each `git commit` and `git push`. In Claude Code and Codex, a PreToolUse hook from `fallow agent install` runs this check and blocks the command when the verdict is `fail`. Other agents run the check themselves: `fallow audit --format json --quiet --explain --gate-marker agent`. If the verdict is `fail`, fix the reported findings before you try again. A JSON runtime error such as `{ "error": true, ... }` does not block.
```

The rest of the block explains the audit gate default and includes the task-to-command matrix from [`fallow schema`](https://fallow.tools/docs/cli/schema/). It is between `<!-- fallow:setup-hooks:start -->` and `<!-- fallow:setup-hooks:end -->` markers. When you upgrade fallow and run the installer again, the block updates in place.

## Why this works well with agents

`fallow audit --explain` gives Claude structured findings with docs links and fix guidance. [Agent Skills](https://fallow.tools/docs/integrations/agent-skills/) teach the agent how to act on them. The loop is short:

1. Claude tries to commit or push.
2. The gate blocks only on `fail`-severity issues.
3. Claude reads the JSON output and fixes the code.
4. Claude tries again.

## See also

- [Adoption rollout](https://fallow.tools/docs/adoption/#7-turn-on-pr-enforcement): See Option C: local agent gate next to warn-everywhere and error-plus-baselines.
- [fallow audit](https://fallow.tools/docs/cli/audit/): Reference for verdicts, JSON output, and baselines.
- [Agent Skills](https://fallow.tools/docs/integrations/agent-skills/): Teach the agent what to do after the gate blocks.
