Skip to content
Workflow · Security

Claude Code hooks guard rails

Fail-closed PreToolUse, post-edit and Stop hooks that block force-pushes, .env reads and wild deletes, check each edit and run tests before done.

Verified install Not agent-tested v1.0.0 MIT updated bashjq
Equips
  • 2 2 Rules
  • 1 1 Skills
  • – 0 MCP

Rules in CLAUDE.md are advice; hooks are enforcement. This loadout installs three small bash + jq hooks that practitioners converged on in autumn 2026: a PreToolUse guard that denies the handful of commands that cause real damage (with a reason the agent can act on), a fast per-file check after every edit, and a Stop gate that runs the tests once and refuses to let the agent finish on red. It uses the hook semantics that matter: only exit 2 or a deny decision blocks, `onFailure: "block"` so a broken hook fails closed, and `stop_hook_active` so the Stop gate can never loop. A self-test proves every block works.

What's in it

  • Bash guard denies force-push, reset --hard, --no-verify, .env access, deletes outside the project and DROP/TRUNCATE
  • Post-edit check on just the changed file (bash -n, jq, ruff, eslint) so errors surface immediately
  • Stop gate runs the test command once, skips clean trees and respects stop_hook_active
  • settings.json snippet with onFailure block, exec-form args and Read deny rules for .env*
  • 33-case self-test, plus a reference on what permissions and hooks really enforce

You'll need

  • Tools: bash, jq, git
Level 1 · automated

Verification

Verified install

Checked at commit 316f4bc · 9 passed, 0 failed, 1 not applicable

CheckResultWhat we found
Listinglisting.yaml matches the schema and declares every file in the folder. Pass
Schema valid; 11 files declared, none missing or extra
  • tested.status: untested (agent tests are separate from Level 1)
Files parseJSON parses; SKILL.md and .mdc front matter is valid YAML with the fields each agent needs; code fences are closed. Pass All 11 files parse; front matter valid
AdaptersCLAUDE.md imports @AGENTS.md, GEMINI.md imports @./AGENTS.md, and both match the generator. Pass
CLAUDE.md and GEMINI.md import AGENTS.md and are up to date
  • CLAUDE.md: matches the generator
  • GEMINI.md: matches the generator
Install pathsEvery file lands where each agent documents it, with no collisions, and the laid-out project is re-discovered correctly. Pass
Correct paths for all 6 agents; layouts re-discovered
  • Claude Code: 9 files, 1 skill in .claude/skills/
  • Codex CLI: 8 files, 1 skill in .agents/skills/
  • Cursor: 9 files, 1 skill in .agents/skills/, 1 rule
  • Gemini CLI: 9 files, 1 skill in .agents/skills/
  • Antigravity CLI: 8 files, 1 skill in .agents/skills/
  • GitHub Copilot: 8 files, 1 skill in .agents/skills/
MCP configmcp.json is structurally valid, converts to each agent's format, declares no literal secrets, and referenced packages exist on npm/PyPI. Not applicable No MCP servers in this loadout
Safety scanThe intake scanner finds no credentials, risky shell, hidden Unicode or prompt-injection wording, and the listing's network flag is honest. Pass
Clean: no credentials, risky shell or hidden text
  • Scanner intake-precheck/1: verdict clean; network yes (listing says yes)
ReferencesLinks and file paths between the loadout's files resolve, including for each agent's installed layout. Pass 3 references between files resolve for every agent
Size limitsFile count and sizes are within the submission limits; SKILL.md bodies stay under 500 lines. Pass
11 files, 19.2 KB total; largest skills/install-guard-hooks/scripts/guard-bash.sh 3.6 KB
  • Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule
LicenceA recognised SPDX licence, with the licence text available. Pass MIT, by Agent Loadout; text in the repository LICENSE
Starter buildA minimal fixture project with the loadout applied installs and builds or type-checks, and the loadout's own code examples compile. Pass
The installed hook scripts make the right decision on 33 sample hook inputs: every guard-bash block, the Stop gate (including stop_hook_active) and the post-edit check
  • Applied 18 loadout files at every agent's paths
  • ✓ bash -n on every hook script: bash -c for f in .claude/skills/install-guard-hooks/scripts/*.sh; do bash -n "$f" || exit 1; done; echo syntax ok
  • ✓ settings snippet is valid JSON: jq empty .claude/skills/install-guard-hooks/references/settings.example.json
  • ✓ self-test (Claude Code layout): bash .claude/skills/install-guard-hooks/scripts/selftest.sh
  • ✓ self-test (.agents layout): bash .agents/skills/install-guard-hooks/scripts/selftest.sh
  • Covers: Force-push, reset --hard, clean -f, --no-verify, .env access, recursive deletes outside the project, DROP/TRUNCATE and unbounded DELETE are denied with a reason
  • Covers: Ordinary commands and deletes inside the project are left to the normal permission flow
  • Covers: The Stop gate blocks on failing tests, passes on green and never blocks when stop_hook_active is true
  • Covers: The post-edit check reports a broken shell or JSON file with exit 2

What this means: a script checked the files themselves: they parse, they install where each agent looks, any MCP config is sound, the safety scan is clean, references between files resolve, and, where a free and repeatable fixture exists, a small starter project builds with the loadout applied. No AI agent was run. Per-agent results ("tested with agent") are separate and stay untested until an agent actually runs.

Install

Choose your agent. Run the commands from your project root, or download a zip already laid out in the right folders.

Claude Code reads CLAUDE.md (imports AGENTS.md with @AGENTS.md) and skills from .claude/skills/.

Download zip
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/claude-code-hooks-guardrails/1.0.0/AGENTS.md
curl -fsSL --create-dirs -o CLAUDE.md https://loadout.jnw-it.com/raw/claude-code-hooks-guardrails/1.0.0/CLAUDE.md
curl -fsSL --create-dirs -o .claude/skills/install-guard-hooks/SKILL.md https://loadout.jnw-it.com/raw/claude-code-hooks-guardrails/1.0.0/skills/install-guard-hooks/SKILL.md
curl -fsSL --create-dirs -o .claude/skills/install-guard-hooks/scripts/guard-bash.sh https://loadout.jnw-it.com/raw/claude-code-hooks-guardrails/1.0.0/skills/install-guard-hooks/scripts/guard-bash.sh
curl -fsSL --create-dirs -o .claude/skills/install-guard-hooks/scripts/post-edit-check.sh https://loadout.jnw-it.com/raw/claude-code-hooks-guardrails/1.0.0/skills/install-guard-hooks/scripts/post-edit-check.sh
curl -fsSL --create-dirs -o .claude/skills/install-guard-hooks/scripts/stop-gate.sh https://loadout.jnw-it.com/raw/claude-code-hooks-guardrails/1.0.0/skills/install-guard-hooks/scripts/stop-gate.sh
curl -fsSL --create-dirs -o .claude/skills/install-guard-hooks/scripts/selftest.sh https://loadout.jnw-it.com/raw/claude-code-hooks-guardrails/1.0.0/skills/install-guard-hooks/scripts/selftest.sh
curl -fsSL --create-dirs -o .claude/skills/install-guard-hooks/references/settings.example.json https://loadout.jnw-it.com/raw/claude-code-hooks-guardrails/1.0.0/skills/install-guard-hooks/references/settings.example.json
curl -fsSL --create-dirs -o .claude/skills/install-guard-hooks/references/permissions.md https://loadout.jnw-it.com/raw/claude-code-hooks-guardrails/1.0.0/skills/install-guard-hooks/references/permissions.md
FileGoes to
AGENTS.mdAGENTS.md
CLAUDE.mdCLAUDE.md
skills/install-guard-hooks/SKILL.md.claude/skills/install-guard-hooks/SKILL.md
skills/install-guard-hooks/scripts/guard-bash.sh.claude/skills/install-guard-hooks/scripts/guard-bash.sh
skills/install-guard-hooks/scripts/post-edit-check.sh.claude/skills/install-guard-hooks/scripts/post-edit-check.sh
skills/install-guard-hooks/scripts/stop-gate.sh.claude/skills/install-guard-hooks/scripts/stop-gate.sh
skills/install-guard-hooks/scripts/selftest.sh.claude/skills/install-guard-hooks/scripts/selftest.sh
skills/install-guard-hooks/references/settings.example.json.claude/skills/install-guard-hooks/references/settings.example.json
skills/install-guard-hooks/references/permissions.md.claude/skills/install-guard-hooks/references/permissions.md

Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.

Files

Rules AGENTS.md How to behave when a hook blocks you, and the hard limits behind the hooks. 28 lines
Download Raw
# AGENTS.md: hooks and permissions guard rails

This project enforces its hard rules with hooks and permission rules, not with prose. Prose in this file guides you;
the hooks decide. Follow both.

## When a hook blocks you
- A blocked tool call comes back with a reason. Read it, then change approach. Do not retry the same command
  with different spelling, quoting, an env-var prefix, `sh -c`, a script file or another tool to get past it.
- If the block looks wrong for this task, stop and tell the human what you tried and the reason you got.
  Only a human edits the hook scripts or `.claude/settings.json`.
- A Stop hook that reports failing checks means you are not done. Fix the cause, not the check.

## Never, even if a hook would let it through
- Rewrite shared history: no `git push --force` to `main`/`master`, no `git reset --hard` on work you did not create.
- Skip verification: no `--no-verify`, no deleting or skipping tests to get green.
- Read or print secrets: `.env`, `.env.*` (except `.env.example`), key files, tokens in shell history.
- Delete outside the project folder, or delete the project folder itself.
- Run migrations, `DROP`, `TRUNCATE` or bulk `DELETE` against a database the human has not named as disposable.

## Keep the loop fast
- After each edit, the post-edit hook runs a quick check on the file you touched (syntax, lint or typecheck).
  Fix what it reports before moving to the next file.
- The full test suite runs once when you finish. Do not run it after every small edit unless the human asks.

## Changing the guard rails
- Use the `install-guard-hooks` skill to install, update or extend the hooks.
- Every new block needs a test: try the forbidden action once and confirm it is denied with a clear reason.
Adapter CLAUDE.md Imports AGENTS.md for Claude Code. 10 lines
Download Raw
# CLAUDE.md

@AGENTS.md

## Claude Code notes
- Skills from this loadout live in `.claude/skills/`: `install-guard-hooks`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "Claude Code hooks guard rails" v1.0.0 — https://loadout.jnw-it.com/loadouts/claude-code-hooks-guardrails/ -->
Adapter GEMINI.md Imports AGENTS.md for Gemini CLI. 10 lines
Download Raw
# GEMINI.md

@./AGENTS.md

## Gemini CLI notes
- Skills from this loadout live in `.agents/skills/`: `install-guard-hooks`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "Claude Code hooks guard rails" v1.0.0 — https://loadout.jnw-it.com/loadouts/claude-code-hooks-guardrails/ -->
Cursor rule .cursor/rules/guard-rails.mdc Always-on Cursor rule with the same hard limits (Cursor has no shell hooks here). 10 lines
Download Raw
---
description: Hard limits that hooks enforce in Claude Code and Codex; Cursor must follow them by hand
alwaysApply: true
---

- No force-push to `main`/`master`, no `git reset --hard` on others' work, no `--no-verify`.
- Never read or print `.env` / `.env.*` (except `.env.example`), keys or tokens.
- Never delete outside the project folder. Never drop, truncate or bulk-delete a database the human has not called disposable.
- If a command is refused, report the reason to the human. Do not rephrase the command to get it through.
Skill skills/install-guard-hooks/SKILL.md Installs, tests and extends the three guard hooks and the permission rules. 46 lines
Download Raw
---
name: install-guard-hooks
description: Install, update or test the project's guard-rail hooks for Claude Code (and Codex). Use when asked to add hooks, block dangerous commands, run tests before finishing, or when a hook misbehaves.
---

# Install guard hooks

Installs three hooks and the matching permission rules, then proves each block works.
Requires `bash` and `jq` on the machine that runs the agent.

| Hook | Event | What it does |
|---|---|---|
| `scripts/guard-bash.sh` | PreToolUse, Bash | Denies force-push, `reset --hard`, `clean -f`, `--no-verify`, `.env` access, recursive deletes outside the project, DROP/TRUNCATE/unbounded DELETE, download-piped-to-shell. Prints a reason. |
| `scripts/post-edit-check.sh` | PostToolUse, Edit/Write | Fast check of the edited file: `bash -n`, `jq empty`, ruff or py_compile, eslint or `node --check`. Exit 2 shows the error to the agent. |
| `scripts/stop-gate.sh` | Stop | Runs the test command once if the tree has changes. Exit 2 keeps the agent working. Skips when `stop_hook_active` is true. |

## Install (Claude Code)

1. Copy the three scripts from this skill's `scripts/` folder to `.claude/hooks/` and make them executable:
   `mkdir -p .claude/hooks && cp <skill-dir>/scripts/{guard-bash,post-edit-check,stop-gate}.sh .claude/hooks/ && chmod +x .claude/hooks/*.sh`
2. Merge `references/settings.example.json` into `.claude/settings.json`. Keep existing keys; append to existing
   `permissions.deny` and `hooks` arrays rather than replacing them. Validate with `jq empty .claude/settings.json`.
3. If the test command is not `npm test`, `pytest -q`, `cargo test -q` or `go test ./...`, write it on one line in
   `.claude/hooks/test-command`.
4. Tell the human to run `/hooks` and confirm the three hooks are listed with source "project settings".

## Install (Codex)

Codex hooks are user-level. Show the human the PreToolUse block from `references/settings.example.json`, adapted
for `~/.codex/hooks.json` with an absolute script path, and let them add it. Do not edit files in their home folder yourself.

## Prove it

1. Run `bash <skill-dir>/scripts/selftest.sh`. It must print `ok:`.
2. In a live session, attempt one forbidden action (for example `git push --force origin scratch`). It must be
   denied with a reason that starts "Blocked by guard-bash". A hook you have not seen block something is not installed.
3. Report which checks were verified and which were skipped (for example, no eslint in the project).

## Extend

- Add a rule to `guard-bash.sh` as a `deny "<reason>"` line, then add a deny and an allow case to `selftest.sh`.
- Reasons must say what to do instead. The agent reads them.
- Keep the post-edit check under a few seconds. Slow checks belong in the Stop gate.
- Read `references/permissions.md` before changing permission rules: deny beats allow, exit 1 does not block,
  and deny rules alone are not a sandbox.
Skill file skills/install-guard-hooks/scripts/guard-bash.sh PreToolUse Bash guard that prints a deny decision with a reason. 76 lines
Download Raw
#!/usr/bin/env bash
# PreToolUse hook for the Bash tool (Claude Code; Codex hooks use the same JSON shape).
# Reads the tool call as JSON on stdin. Prints a "deny" decision with a reason, or nothing to defer
# to the normal permission flow. Needs jq. Register it with onFailure "block" so a crash fails closed.
set -u
input=$(cat)
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty')
[ -z "$cmd" ] && exit 0
project="${CLAUDE_PROJECT_DIR:-$(printf '%s' "$input" | jq -r '.cwd // empty')}"
project="${project%/}"
flat=$(printf '%s' "$cmd" | tr '\n\t' '  ')
lower=$(printf '%s' "$flat" | tr '[:upper:]' '[:lower:]')

deny() {
  jq -n --arg r "Blocked by guard-bash: $1" \
    '{hookSpecificOutput: {hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: $r}}'
  exit 0
}

# 1. Shared git history and skipped checks.
if [[ "$lower" =~ git[[:space:]].*push ]]; then
  if [[ "$lower" =~ --force([[:space:]]|$) || "$lower" =~ [[:space:]]-[a-z]*f([[:space:]]|$) || "$lower" =~ [[:space:]]\+[a-z0-9/_.-]+ ]]; then
    deny "force-push rewrites shared history. Push a new commit, or ask the human."
  fi
  if [[ "$lower" =~ --force-with-lease && "$lower" =~ (^|[[:space:]:/])(main|master)([[:space:]]|$) ]]; then
    deny "force-push to main/master. Ask the human."
  fi
fi
[[ "$lower" =~ git[[:space:]]+reset[[:space:]].*--hard ]] && deny "git reset --hard discards work. Use git stash or a new branch, or ask the human."
[[ "$lower" =~ git[[:space:]]+clean[[:space:]]+-[a-z]*f ]] && deny "git clean -f deletes untracked files. List them with git clean -n and ask the human."
[[ "$lower" =~ --no-verify ]] && deny "--no-verify skips the project's checks. Fix what the checks report."

# 2. Secrets. .env.example / .sample / .template are fine.
if [[ "$flat" =~ (^|[[:space:]/=\'\"])\.env(\.[A-Za-z0-9_-]+)?([[:space:]\'\";|]|$) ]]; then
  m="${BASH_REMATCH[0]}"
  [[ "$m" =~ \.env\.(example|sample|template) ]] || deny "commands may not touch .env files; they hold secrets. Use .env.example for names."
fi

# 3. Recursive deletes outside the project, of the project itself, or via ~, $HOME or ..
read -r -a words <<< "$flat"
in_rm=0; recursive=0; targets=()
for w in "${words[@]}"; do
  case "$w" in
    rm|*/rm) in_rm=1; recursive=0; continue ;;
    ';'|'&&'|'||'|'|') in_rm=0; continue ;;
  esac
  [ "$in_rm" = 1 ] || continue
  if [[ "$w" == -* ]]; then [[ "$w" =~ ^-[a-zA-Z]*[rR] || "$w" == "--recursive" ]] && recursive=1; continue; fi
  [ "$recursive" = 1 ] && targets+=("$w")
done
for t in "${targets[@]:-}"; do
  [ -z "$t" ] && continue
  t="${t%\"}"; t="${t#\"}"; t="${t%\'}"; t="${t#\'}"
  case "$t" in
    '~'*|'$HOME'*|'${HOME}'*) deny "recursive delete under the home directory ($t)." ;;
    *..*) deny "recursive delete with .. in the path ($t). Use a path inside the project." ;;
    .|./|'*'|./'*') deny "recursive delete of the whole project folder ($t)." ;;
    /*)
      if [ -z "$project" ] || [[ "$t" != "$project"/* ]]; then deny "recursive delete outside the project ($t)."; fi ;;
  esac
done

# 4. Destructive SQL from the shell.
if [[ "$lower" =~ (drop[[:space:]]+(table|database|schema)|truncate[[:space:]]+) ]]; then
  deny "DROP/TRUNCATE from the shell. Write a migration file and ask the human to run it."
fi
if [[ "$lower" =~ delete[[:space:]]+from[[:space:]] && ! "$lower" =~ [[:space:]]where[[:space:]] ]]; then
  deny "DELETE without WHERE. Add a WHERE clause or ask the human."
fi

# 5. Downloaded code piped straight into a shell.
if [[ "$lower" =~ (curl|wget)[^\|]*\|[[:space:]]*(sudo[[:space:]]+)?(ba|z|da)?sh([[:space:]]|$) ]]; then
  deny "piping a download into a shell. Download, read, then run it, or ask the human."
fi
exit 0
Skill file skills/install-guard-hooks/scripts/post-edit-check.sh PostToolUse fast check of the edited file. 27 lines
Download Raw
#!/usr/bin/env bash
# PostToolUse hook for Edit|Write: a fast check on the one file that changed.
# Exit 2 sends stderr back to the agent so it fixes the file before moving on. Missing tools are skipped.
set -u
input=$(cat)
file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty')
[ -n "$file" ] && [ -f "$file" ] || exit 0
have() { command -v "$1" >/dev/null 2>&1; }
root="${CLAUDE_PROJECT_DIR:-$PWD}"
out=""
case "$file" in
  *.sh|*.bash) out=$(bash -n "$file" 2>&1) ;;
  *.json) if have jq; then out=$(jq empty "$file" 2>&1); fi ;;
  *.py)
    if have ruff; then out=$(ruff check --quiet "$file" 2>&1)
    elif have python3; then out=$(python3 -m py_compile "$file" 2>&1); fi ;;
  *.ts|*.tsx|*.js|*.jsx|*.mjs|*.cjs)
    if [ -x "$root/node_modules/.bin/eslint" ]; then out=$("$root/node_modules/.bin/eslint" --no-warn-ignored "$file" 2>&1)
    elif [[ "$file" =~ \.(js|mjs|cjs)$ ]] && have node; then out=$(node --check "$file" 2>&1); fi ;;
esac
status=$?
if [ $status -ne 0 ]; then
  printf 'post-edit check failed for %s:\n%s\n' "$file" "$(printf '%s' "$out" | tail -n 30)" >&2
  exit 2
fi
exit 0
Skill file skills/install-guard-hooks/scripts/stop-gate.sh Stop hook that runs the tests once and respects stop_hook_active. 28 lines
Download Raw
#!/usr/bin/env bash
# Stop hook: run the project's tests once when the agent says it is done.
# Exit 2 keeps the agent working and shows it the failures. Respects stop_hook_active so it never loops.
set -u
input=$(cat)
[ "$(printf '%s' "$input" | jq -r '.stop_hook_active // false')" = "true" ] && exit 0
root="${CLAUDE_PROJECT_DIR:-$(printf '%s' "$input" | jq -r '.cwd // empty')}"
cd "${root:-.}" || exit 0
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
[ -z "$(git status --porcelain 2>/dev/null)" ] && exit 0   # nothing changed, nothing to gate

test_cmd="${GUARD_TEST_CMD:-}"
[ -z "$test_cmd" ] && [ -f .claude/hooks/test-command ] && test_cmd=$(head -n 1 .claude/hooks/test-command)
if [ -z "$test_cmd" ]; then
  if [ -f package.json ] && jq -e '.scripts.test' package.json >/dev/null 2>&1; then test_cmd="npm test --silent"
  elif [ -f pyproject.toml ] && command -v pytest >/dev/null 2>&1; then test_cmd="pytest -q"
  elif [ -f Cargo.toml ]; then test_cmd="cargo test -q"
  elif [ -f go.mod ]; then test_cmd="go test ./..."
  else exit 0; fi
fi
runner=(bash -c "$test_cmd")
command -v timeout >/dev/null 2>&1 && runner=(timeout "${GUARD_TEST_TIMEOUT:-600}" bash -c "$test_cmd")
if ! out=$("${runner[@]}" 2>&1); then
  printf 'Tests failed (%s). You are not done; fix the cause:\n%s\n' "$test_cmd" "$(printf '%s' "$out" | tail -n 40)" >&2
  exit 2
fi
exit 0
Skill file skills/install-guard-hooks/scripts/selftest.sh Feeds sample hook input to each script and checks every decision. 62 lines
Download Raw
#!/usr/bin/env bash
# Self-test for the guard hooks. Feeds sample hook input to each script and checks the decision.
# Run from anywhere: bash path/to/scripts/selftest.sh   Prints "ok: N cases" or the first failures.
set -u
here="$(cd "$(dirname "$0")" && pwd)"
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
export CLAUDE_PROJECT_DIR="$tmp/project"; mkdir -p "$CLAUDE_PROJECT_DIR"
fails=0; n=0
bash_call() { jq -nc --arg c "$1" --arg d "$CLAUDE_PROJECT_DIR" '{hook_event_name:"PreToolUse",tool_name:"Bash",cwd:$d,tool_input:{command:$c}}'; }
expect_guard() { # expect_guard deny|allow "command"
  n=$((n+1))
  out=$(bash_call "$2" | bash "$here/guard-bash.sh")
  got=allow; [ -n "$out" ] && got=$(printf '%s' "$out" | jq -r '.hookSpecificOutput.permissionDecision')
  [ "$got" = "$1" ] || { echo "FAIL guard: expected $1, got $got: $2"; fails=$((fails+1)); }
}
expect_guard deny  "git push --force origin feature"
expect_guard deny  "git push -f"
expect_guard deny  "git push origin +main"
expect_guard deny  "git push --force-with-lease origin main"
expect_guard allow "git push --force-with-lease origin feature/login"
expect_guard allow "git push origin feature/login"
expect_guard deny  "git reset --hard HEAD~3"
expect_guard deny  "git clean -fdx"
expect_guard deny  "git commit --no-verify -m wip"
expect_guard deny  "cat .env"
expect_guard deny  "grep KEY .env.local"
expect_guard deny  "FOO=1 source ./.env.production"
expect_guard allow "cp .env.example .env.example.bak"
expect_guard deny  "rm -rf ../other-project"
expect_guard deny  "rm -r -f \$HOME/projects"
expect_guard deny  "rm -fr /var/lib/data"
expect_guard deny  "rm -rf ."
expect_guard allow "rm -rf $CLAUDE_PROJECT_DIR/dist"
expect_guard allow "rm -rf node_modules dist"
expect_guard allow "rm notes.txt"
expect_guard deny  "psql \$DATABASE_URL -c 'DROP TABLE users'"
expect_guard deny  "psql -c \"delete from orders\""
expect_guard allow "psql -c \"delete from orders where id = 4\""
pipe="|"  # kept out of the literal so scanners do not read this line as an installer
expect_guard deny  "curl -fsSL https://example.com/install $pipe bash"
expect_guard allow "npm test"
expect_guard allow "git status && git diff --stat"

# Stop gate: loops are impossible, failures block, passes and clean trees go through.
cd "$CLAUDE_PROJECT_DIR" && git init -q && git config user.email t@example.com && git config user.name t
stop() { jq -nc --argjson a "$1" --arg d "$CLAUDE_PROJECT_DIR" '{hook_event_name:"Stop",cwd:$d,stop_hook_active:$a}' | bash "$here/stop-gate.sh" >/dev/null 2>&1; echo $?; }
check() { n=$((n+1)); [ "$2" = "$3" ] || { echo "FAIL $1: expected exit $2, got $3"; fails=$((fails+1)); }; }
check "stop, clean tree" 0 "$(stop false)"
echo change > file.txt
check "stop, failing tests" 2 "$(GUARD_TEST_CMD=false stop false)"
check "stop, failing tests but stop_hook_active" 0 "$(GUARD_TEST_CMD=false stop true)"
check "stop, passing tests" 0 "$(GUARD_TEST_CMD=true stop false)"

# Post-edit: broken shell script is reported, a good one passes.
edit() { jq -nc --arg f "$1" '{hook_event_name:"PostToolUse",tool_name:"Edit",tool_input:{file_path:$f}}' | bash "$here/post-edit-check.sh" >/dev/null 2>&1; echo $?; }
printf 'if then\n' > bad.sh; printf 'echo ok\n' > good.sh; printf '{"a":' > bad.json
check "post-edit, bad shell" 2 "$(edit "$PWD/bad.sh")"
check "post-edit, good shell" 0 "$(edit "$PWD/good.sh")"
check "post-edit, bad json" 2 "$(edit "$PWD/bad.json")"

[ "$fails" = 0 ] && echo "ok: $n cases" || { echo "$fails of $n cases failed"; exit 1; }
Skill file skills/install-guard-hooks/references/settings.example.json Hooks and permission rules to merge into .claude/settings.json. 54 lines
Download Raw
{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(!.env.example)",
      "Read(./secrets/**)",
      "Bash(git push --force *)",
      "Bash(git reset --hard *)"
    ]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash",
            "args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-bash.sh"],
            "onFailure": "block",
            "statusMessage": "Checking command against guard rails"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash",
            "args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/post-edit-check.sh"],
            "timeout": 60
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash",
            "args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/stop-gate.sh"],
            "timeout": 900
          }
        ]
      }
    ]
  }
}
Skill file skills/install-guard-hooks/references/permissions.md What permissions and hooks really enforce, with version notes. 42 lines
Download Raw
# Permissions and hooks: what actually holds (Claude Code, October 2026)

Facts from the Claude Code permissions and hooks reference. Check `claude --version` before relying on a version-gated item.

## Order of evaluation
- Deny rules beat allow rules. An allow rule cannot carve an exception out of a deny rule.
- A PreToolUse hook that denies, or exits 2, blocks the call even if an allow rule matches.
- A hook that returns "allow" does not bypass a matching deny or ask rule.
- Exception: an installed mod that handles `tool.check` answers after rules and hooks and can replace their answer.
  Review mods before installing them; a mod can switch your guard rails off.

## Exit codes
- Exit 0: success. JSON on stdout is applied.
- Exit 2: blocking error. stderr is the reason shown to the agent.
- Any other exit code: non-blocking error. The tool call goes ahead. Do not use `exit 1` to enforce a policy.
- `onFailure: "block"` (v2.1.295+) makes a crash, a missing script, invalid JSON or a timeout block the call.
  Use it on every policy hook. It has no effect on Stop hooks.

## Matching
- Deny and ask rules match past any leading `VAR=value` assignment (fixed in v2.1.289).
- Allow rules only skip a short built-in list of safe variables, so `FOO=bar npm test` is not covered by `Bash(npm test *)`.
- Environment runners (`npx`, `docker exec`, `devbox run`, `mise exec`) run whatever follows them. Allow the exact inner command.
- Hook `if` filters are best-effort. Enforce hard rules with permission rules or with a hook that inspects the whole command.

## Deny rules are not a sandbox
- `Read(./.env)` stops the Read tool. A shell command, a script, or an `@import` can still reach the file.
  Pair Read deny rules with the Bash guard hook, and with the sandbox's denied read paths where available.
- Cover `.env`, `.env.*` and any secrets folder. Carve out `.env.example` with a `!` rule in the same file.

## Settings files merge
- Permission rules merge across user, project and local settings. An allow rule in `~/.claude/settings.json` applies in every repo.
- Hooks merge too; the same handler defined twice runs once.
- Project `.claude/settings.json` hooks run in `claude -p` sessions without a trust prompt. Use `--bare` for scripted runs in repos you do not control.

## Stop hooks
- The Stop input has `stop_hook_active: true` when the agent is already continuing because of a Stop hook. Exit 0 then, or the agent can loop.
- Claude Code also caps consecutive Stop-hook continuations at 8.

## Codex
- Codex reads hooks from `~/.codex/hooks.json` or a `[hooks]` table in `~/.codex/config.toml`, with the same event names.
- A PreToolUse hook blocks with exit 2 or `permissionDecision: "deny"`. Test the Bash input shape with one harmless call before relying on it.

All files: source zip · Licence MIT · By Agent Loadout · First listed

Credits

Public work that inspired this loadout. The wording here is our own; follow the links for the originals.

Installing and testing

How do I install Claude Code hooks guard rails in Claude Code?

From your project root, put AGENTS.md, CLAUDE.md, skills/install-guard-hooks/SKILL.md at .claude/skills/install-guard-hooks/SKILL.md, skills/install-guard-hooks/scripts/guard-bash.sh at .claude/skills/install-guard-hooks/scripts/guard-bash.sh, skills/install-guard-hooks/scripts/post-edit-check.sh at .claude/skills/install-guard-hooks/scripts/post-edit-check.sh, skills/install-guard-hooks/scripts/stop-gate.sh at .claude/skills/install-guard-hooks/scripts/stop-gate.sh, skills/install-guard-hooks/scripts/selftest.sh at .claude/skills/install-guard-hooks/scripts/selftest.sh, skills/install-guard-hooks/references/settings.example.json at .claude/skills/install-guard-hooks/references/settings.example.json, skills/install-guard-hooks/references/permissions.md at .claude/skills/install-guard-hooks/references/permissions.md. The "Claude Code" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/claude-code-hooks-guardrails/claude-code.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Claude Code hooks guard rails in Codex CLI?

From your project root, put AGENTS.md, skills/install-guard-hooks/SKILL.md at .agents/skills/install-guard-hooks/SKILL.md, skills/install-guard-hooks/scripts/guard-bash.sh at .agents/skills/install-guard-hooks/scripts/guard-bash.sh, skills/install-guard-hooks/scripts/post-edit-check.sh at .agents/skills/install-guard-hooks/scripts/post-edit-check.sh, skills/install-guard-hooks/scripts/stop-gate.sh at .agents/skills/install-guard-hooks/scripts/stop-gate.sh, skills/install-guard-hooks/scripts/selftest.sh at .agents/skills/install-guard-hooks/scripts/selftest.sh, skills/install-guard-hooks/references/settings.example.json at .agents/skills/install-guard-hooks/references/settings.example.json, skills/install-guard-hooks/references/permissions.md at .agents/skills/install-guard-hooks/references/permissions.md. The "Codex CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/claude-code-hooks-guardrails/codex.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Claude Code hooks guard rails in Cursor?

From your project root, put AGENTS.md, .cursor/rules/guard-rails.mdc, skills/install-guard-hooks/SKILL.md at .agents/skills/install-guard-hooks/SKILL.md, skills/install-guard-hooks/scripts/guard-bash.sh at .agents/skills/install-guard-hooks/scripts/guard-bash.sh, skills/install-guard-hooks/scripts/post-edit-check.sh at .agents/skills/install-guard-hooks/scripts/post-edit-check.sh, skills/install-guard-hooks/scripts/stop-gate.sh at .agents/skills/install-guard-hooks/scripts/stop-gate.sh, skills/install-guard-hooks/scripts/selftest.sh at .agents/skills/install-guard-hooks/scripts/selftest.sh, skills/install-guard-hooks/references/settings.example.json at .agents/skills/install-guard-hooks/references/settings.example.json, skills/install-guard-hooks/references/permissions.md at .agents/skills/install-guard-hooks/references/permissions.md. The "Cursor" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/claude-code-hooks-guardrails/cursor.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Claude Code hooks guard rails in Gemini CLI?

From your project root, put AGENTS.md, GEMINI.md, skills/install-guard-hooks/SKILL.md at .agents/skills/install-guard-hooks/SKILL.md, skills/install-guard-hooks/scripts/guard-bash.sh at .agents/skills/install-guard-hooks/scripts/guard-bash.sh, skills/install-guard-hooks/scripts/post-edit-check.sh at .agents/skills/install-guard-hooks/scripts/post-edit-check.sh, skills/install-guard-hooks/scripts/stop-gate.sh at .agents/skills/install-guard-hooks/scripts/stop-gate.sh, skills/install-guard-hooks/scripts/selftest.sh at .agents/skills/install-guard-hooks/scripts/selftest.sh, skills/install-guard-hooks/references/settings.example.json at .agents/skills/install-guard-hooks/references/settings.example.json, skills/install-guard-hooks/references/permissions.md at .agents/skills/install-guard-hooks/references/permissions.md. The "Gemini CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/claude-code-hooks-guardrails/gemini-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Claude Code hooks guard rails in Antigravity CLI?

From your project root, put AGENTS.md, skills/install-guard-hooks/SKILL.md at .agents/skills/install-guard-hooks/SKILL.md, skills/install-guard-hooks/scripts/guard-bash.sh at .agents/skills/install-guard-hooks/scripts/guard-bash.sh, skills/install-guard-hooks/scripts/post-edit-check.sh at .agents/skills/install-guard-hooks/scripts/post-edit-check.sh, skills/install-guard-hooks/scripts/stop-gate.sh at .agents/skills/install-guard-hooks/scripts/stop-gate.sh, skills/install-guard-hooks/scripts/selftest.sh at .agents/skills/install-guard-hooks/scripts/selftest.sh, skills/install-guard-hooks/references/settings.example.json at .agents/skills/install-guard-hooks/references/settings.example.json, skills/install-guard-hooks/references/permissions.md at .agents/skills/install-guard-hooks/references/permissions.md. The "Antigravity CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/claude-code-hooks-guardrails/antigravity-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Claude Code hooks guard rails in GitHub Copilot?

From your project root, put AGENTS.md, skills/install-guard-hooks/SKILL.md at .agents/skills/install-guard-hooks/SKILL.md, skills/install-guard-hooks/scripts/guard-bash.sh at .agents/skills/install-guard-hooks/scripts/guard-bash.sh, skills/install-guard-hooks/scripts/post-edit-check.sh at .agents/skills/install-guard-hooks/scripts/post-edit-check.sh, skills/install-guard-hooks/scripts/stop-gate.sh at .agents/skills/install-guard-hooks/scripts/stop-gate.sh, skills/install-guard-hooks/scripts/selftest.sh at .agents/skills/install-guard-hooks/scripts/selftest.sh, skills/install-guard-hooks/references/settings.example.json at .agents/skills/install-guard-hooks/references/settings.example.json, skills/install-guard-hooks/references/permissions.md at .agents/skills/install-guard-hooks/references/permissions.md. The "GitHub Copilot" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/claude-code-hooks-guardrails/copilot.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

Has Claude Code hooks guard rails been tested?

It passed the Level 1 automated install checks on 2026-10-17: the files parse, install to each agent's documented paths, and pass the safety scan. No AI agent has run it yet, so no agent pass or fail is claimed.

More for Claude Code, Codex, Cursor, Gemini CLI, Antigravity CLI and GitHub Copilot.