Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.fallow.tools/llms.txt

Use this file to discover all available pages before exploring further.

Claude Code hooks let you insert deterministic checks into the agent loop. This pattern is not a Git hook. It is a local PreToolUse gate on Claude’s Bash tool. When Claude tries git commit or git push, the hook runs fallow audit --format json --quiet --explain. pass and warn are allowed. fail is blocked, and the JSON findings are sent back to Claude on stderr so it can fix the code and retry.
This is Option C: local agent gate in the adoption rollout. Use it together with the shared CI gate from fallow audit, not instead of it. See Adopt Fallow in an existing repo.

Install it in one command

fallow hooks install --target agent
That command writes a project-level .claude/settings.json and .claude/hooks/fallow-gate.sh. It detects whether your repo also has an AGENTS.md or .codex/ and, if so, adds a managed Codex fallback block to AGENTS.md as well. To remove the generated artifacts later, run fallow hooks uninstall --target agent. Preview first with --dry-run:
fallow hooks install --target agent --dry-run
Output looks like this, with one line per affected path and a description of what changed:
fallow hooks install --target agent (install):
  .claude/settings.json          updated (1 handler added, 1 preserved)
  .claude/hooks/fallow-gate.sh   created
  AGENTS.md                      managed block inserted

Install globally for every repo

Pass --user to install the gate under your home directory instead of per-project. The settings file lands at ~/.claude/settings.json, the hook script at ~/.claude/hooks/fallow-gate.sh, and the emitted handler command uses "$HOME" in place of "$CLAUDE_PROJECT_DIR" so it resolves in every repo Claude Code opens:
fallow hooks install --target agent --user
Project-level installs override the global one when both are present, so you can still opt a specific repo into a stricter or laxer policy.

Gate semantics

The hook runs fallow audit --format json --quiet --explain and blocks the agent’s git commit or git push only when the audit returns verdict: "fail". Audit defaults to gate=new-only: inherited findings on touched files are reported in attribution and annotated with introduced: false on individual issues, but they do not affect the verdict. Only findings the changeset introduces fail the gate. This is the adoption-friendly default for legacy codebases: an agent editing a 10-year-old file is not punished for the existing dead exports already there, only for any new ones it adds. To gate every finding in changed files (the strict mode), set the gate in your project config:
fallow.toml
[audit]
gate = "all"
Or override per invocation by editing .claude/hooks/fallow-gate.sh to pass --gate all explicitly. The script is regenerated on every fallow hooks install --target agent run, so prefer the config-file approach for persistence.

Version floor

The installed gate enforces a minimum fallow version via the FALLOW_GATE_MIN_VERSION env var (default 2.46.0). Older binaries miss the uncommitted-changes inclusion fix and can silently pass audits that should fail, so the gate blocks with an upgrade hint when the fallow on PATH is below the floor. This prevents a stale npm-global or ~/.cargo/bin/fallow from masquerading as a working gate.
# Override (require a newer fallow across your team)
export FALLOW_GATE_MIN_VERSION=2.48.1

# Disable (only do this if you know why)
export FALLOW_GATE_MIN_VERSION=
Every block logs the binary path and version that fired it, e.g. fallow-gate: blocked by fallow 2.48.1 at /usr/local/bin/fallow, so “which binary ran this?” is answered without re-diagnosis. The header of the installed script also stamps # Installer version: X.Y.Z for forensics when the gate disagrees with your current fallow --version.

Remove it

fallow hooks uninstall --target agent
Removes the fallow-gate handler from .claude/settings.json, deletes the hook script if it still carries the generator marker (use --force to remove user-edited scripts), and strips the managed block from AGENTS.md. Handlers you added outside fallow stay untouched. Pass --user to uninstall the global artifacts at ~/.claude/ instead of the project ones. The uninstall flow is idempotent: running it a second time prints unchanged / not present and exits 0. Dry-run is supported (fallow hooks uninstall --target agent --dry-run). fallow hooks install --target agent also recognises and cleans up hook commands that older manual setups emitted (absolute paths, ~/-based paths, Windows drive paths), so re-running it on a pre-existing install upgrades the handler to the canonical form without leaving duplicates.
Related but distinct: fallow hooks install --target git scaffolds a shell-level Git pre-commit hook in .git/hooks/ that runs fallow on changed files. That is the human enforcement path. This page is the agent enforcement path. The two can be used together: git hooks catch human commits, the agent gate catches agent commits.

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

The canonical source of this file lives in the fallow repo at crates/cli/src/setup_hooks/fallow-gate.sh. For a hands-off install, prefer fallow hooks install --target agent; the inline listing below is for environments where you cannot run that command. When copied by hand, omit the # Installer version: line (it is stamped automatically by the installer for forensics).
#!/usr/bin/env bash
set -euo pipefail

# Generated by fallow setup-hooks.
# Requires bash and jq. On Windows run via git-bash or WSL.
# Blocks Claude Code git commit and git push when fallow audit returns verdict fail.
# Runtime errors fail open with a single stderr notice so skips stay visible.
#
# Version floor (FALLOW_GATE_MIN_VERSION, default 2.46.0). Older binaries miss
# the uncommitted-changes inclusion fix (aabb8e1b) and can silently pass
# audits that should fail. Set the env var to the empty string to disable.
# Floor comparison uses `sort -V`; GNU and BSD agree on plain semver but
# diverge on prereleases (BSD sorts `2.48.0-alpha.1` ABOVE `2.48.0`, GNU below).
# If you set a prerelease floor explicitly, verify the behavior on the target OS.

if ! command -v jq >/dev/null 2>&1; then
  echo "fallow-gate: jq not on PATH, skipping audit." >&2
  exit 0
fi

INPUT="$(cat)"
CMD="$(jq -r '.tool_input.command // empty' <<<"$INPUT")"

if ! printf '%s\n' "$CMD" | grep -Eq '(^|[[:space:];|&()])git[[:space:]]+(commit|push)([[:space:]]|$)'; then
  exit 0
fi

if command -v fallow >/dev/null 2>&1; then
  RUNNER=(fallow)
  BIN_DESC="$(command -v fallow)"
elif command -v npx >/dev/null 2>&1 && VER_PROBE="$(npx --no-install fallow --version 2>/dev/null || true)" && [[ "$VER_PROBE" == fallow* ]]; then
  RUNNER=(npx --no-install fallow)
  BIN_DESC="npx --no-install fallow"
else
  echo "fallow-gate: fallow binary not found (tried PATH and npx --no-install), skipping audit." >&2
  exit 0
fi

VERSION_RAW="$("${RUNNER[@]}" --version 2>/dev/null || true)"
VERSION="${VERSION_RAW#fallow }"
VERSION="${VERSION%% *}"

MIN_VERSION="${FALLOW_GATE_MIN_VERSION-2.46.0}"
if [ -n "$MIN_VERSION" ] && [ -n "$VERSION" ]; then
  LOWER="$(printf '%s\n%s\n' "$MIN_VERSION" "$VERSION" | sort -V | head -n1)"
  if [ "$LOWER" != "$MIN_VERSION" ]; then
    {
      echo "fallow-gate: blocked: $BIN_DESC is fallow $VERSION, below required $MIN_VERSION."
      echo "fallow-gate: older binaries miss the uncommitted-changes fix (v2.46.0) and can"
      echo "fallow-gate: silently pass audits that would otherwise fail."
      echo "fallow-gate: upgrade the fallow on PATH (e.g. npm install -g fallow@latest or"
      echo "fallow-gate: cargo install fallow-cli), or set FALLOW_GATE_MIN_VERSION= to disable."
    } >&2
    exit 2
  fi
fi

TMP_JSON="$(mktemp)"
TMP_ERR="$(mktemp)"
cleanup() {
  rm -f "$TMP_JSON" "$TMP_ERR"
}
trap cleanup EXIT

if "${RUNNER[@]}" audit --format json --quiet --explain >"$TMP_JSON" 2>"$TMP_ERR"; then
  STATUS=0
else
  STATUS=$?
fi

VERDICT="$(jq -r '.verdict // empty' <"$TMP_JSON" 2>/dev/null || true)"
IS_ERROR="$(jq -r '.error // false' <"$TMP_JSON" 2>/dev/null || echo false)"

if [ "$VERDICT" = "fail" ]; then
  echo "fallow-gate: blocked by fallow ${VERSION:-unknown} at $BIN_DESC" >&2
  cat "$TMP_JSON" >&2
  exit 2
fi

if [ "$STATUS" -eq 2 ] || [ "$IS_ERROR" = "true" ]; then
  MSG="$(jq -r '.message // empty' <"$TMP_JSON" 2>/dev/null || true)"
  if [ -n "$MSG" ]; then
    echo "fallow-gate: fallow audit runtime error ($MSG), skipping." >&2
  else
    echo "fallow-gate: fallow audit runtime error, skipping." >&2
  fi
  exit 0
fi

if [ "$STATUS" -ne 0 ]; then
  ERR_LINE="$(sed -n '1p' "$TMP_ERR" 2>/dev/null || true)"
  if [ -n "$ERR_LINE" ]; then
    echo "fallow-gate: fallow audit exited $STATUS ($ERR_LINE), skipping." >&2
  else
    echo "fallow-gate: fallow audit exited $STATUS, skipping." >&2
  fi
  exit 0
fi

exit 0

3. Make the script executable

chmod +x .claude/hooks/fallow-gate.sh
The hook checks fallow first, then falls back to npx --no-install fallow. Keep jq on PATH because the hook reads Claude’s stdin JSON and Fallow’s JSON output. Skip paths print one stderr line so the agent does not silently trust a missing gate.
Runtime errors fail open on purpose. In JSON mode, fallow audit reports invalid refs, non-Git directories, and config errors as { "error": true, ... } with exit code 2. Letting those cases through prevents a brand-new repo or empty history from getting stuck before the first successful audit.
This gate runs before Claude executes the shell command, so git push --no-verify does not bypass it. Keep CI on fallow audit as the shared gate for humans and non-Claude workflows.

Codex fallback

Codex now has an experimental hooks surface, but the low-friction fallback while it stabilizes is still AGENTS.md. Add this to your repo root AGENTS.md:
## Fallow local gate

Before any `git commit` or `git push`, run `fallow audit --format json --quiet --explain`. 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 maintains this block for you, scoped between <!-- fallow:setup-hooks:start --> and <!-- fallow:setup-hooks:end --> markers so re-running the command is idempotent.

Why this works well with agents

fallow audit --explain gives Claude structured findings with docs links and fix guidance, while Agent Skills teaches the agent how to act on them. The result is a tight feedback loop: Claude tries to commit or push, the gate blocks only real fail-severity issues, Claude reads the JSON envelope, fixes the code, and retries.

Adoption rollout

See Option C: local agent gate next to warn-everywhere and error-plus-baselines.

fallow audit

Reference for verdicts, JSON output, and baselines.

Agent Skills

Teach the agent what to do after the gate blocks.