Teams add Claude Code hooks so AI agents cannot do damage, and then assume they work. A hook can pass on every run while doing nothing. A hook that silently does nothing is worse than no hook, because everyone stops looking. The fix is four cheap checks any team can copy.
The short answer: a hook goes silent when its output goes nowhere, when its path does not resolve, or when its exit code gets rewritten. Check all three in CI. Then put the hook that matters most on the Stop event, so the agent cannot say "done" while tests are red.
How do Claude Code hooks block anything?
A hook is a shell command that Claude Code runs on an event. PreToolUse runs before a tool call, Stop when the agent wants to finish. The command gets the event as a JSON object on stdin.
The exit code decides:
Exit 0 lets it pass.
Exit 2 blocks. What the hook wrote to stderr goes back to the agent as the reason.
Any other code is a non-blocking error. Claude Code carries on.
That is the whole contract. Everything that goes wrong with hooks goes wrong inside it.
Why does a hook silently do nothing?
A hook can stay silent from day one without anyone noticing. Usually for three reasons, each harmless on its own:
Muted. The hook writes its only message to stderr, but the command in the settings ends with 2>/dev/null. The agent hears nothing.
Missing. The hook uses a relative path. In a session started inside a nested repository, the path does not exist. Exit 127, command not found.
Masked.|| true at the end turns 127 into 0. On a blocking event it turns 2 into 0 just the same. Every run "passes".
Nobody notices, because a silent hook looks exactly like a healthy one. Only an analysis of Claude Code sessions or a check in CI finds it.
Add a check to your validator that runs in the CI of every repository. Four greps over .claude/settings.json, nothing is executed:
Check
What it catches
Fix
The script exists
A path that does not resolve
"$CLAUDE_PROJECT_DIR"/... or an absolute path
The script is executable
A hook that only runs by luck
chmod +x
stderr is not discarded when the script writes to stderr
A muted hook
Drop 2>/dev/null
A PreToolUse hook does not end in || true
A block rewritten into a pass
Drop || true
Test the check on a real bug: a broken config must report critical errors, the fixed one must pass.
Not every || true is a bug. A greeting hook on SessionStart can use 2>/dev/null || true on purpose, because a greeting must never break a session. The check should only flag places where a block is expected.
The lesson in one line, as it stands in our attribution hook: "A rule is a request. This is the lock." And a lock needs its own test.
Not long ago the agent had its rules only in rule files, and sometimes it ignored them. So we turned them into checks it cannot get around.
Filip Papranec
Founder, Digitaliko
Which hook actually gates?
The Stop hook. An agent that says "done" while tests are red costs a senior a review round. A Stop hook makes the agent fix it first.
Our version from the public starter, slightly shortened:
#!/bin/bash# Stop hook: the agent can't finish while the checks are red.in=$(cat)[ "$(printf '%s' "$in" | jq -r '.stop_hook_active // false')"=true ] &&exit 0cd"${CLAUDE_PROJECT_DIR:-.}"2>/dev/null ||exit 0out=$(GATE_SKIP_INSTALL=1 GATE_SCRIPTS="typecheck lint test" bash scripts/gate.sh checks 2>&1)&&exit 0jq -n --arg r "Checks are red. Fix them before you say the task is done:$out"'{decision: "block", reason: $r}'
Step by step it does this:
01
Reads stop_hook_active
If it is true, the agent is already continuing because of this hook. The hook lets it go, so it never loops.
02
Runs the checks
Typecheck, lint and tests, the same as in CI.
03
Blocks with a reason
When the checks are red, it returns decision: block with the failure log as the reason.
04
The agent fixes the cause
It reads the log, fixes the error and tries to finish again.
It blocks with JSON, not an exit code. The agent keeps working, and the reason it gets is exactly what failed.
It is an early warning, not the gate. The authoritative check should run again after the session, in code the agent cannot touch. The hook only saves a round.
What to watch for in a Stop hook?
A Stop hook can have a silent bug of its own. A fixed, minimal PATH without the user tool directories means repositories on Bun miss the tools the checks need. A gate looked up in one fixed place only works on one machine. Set the full PATH and look up the gate relative to the hook itself.
Claude Code hooks for a team: where to start?
Go through what your repositories already have. Each point is a few lines in settings or in CI:
The third point is the easiest to miss. Our attribution hook reads its input with jq, falls back to python3, then to sed. A missing jq must never turn the lock into a silent allow. Relative paths, in turn, break in nested repositories and worktrees.
We set up hooks, gates and CI right in a team's repository as part of AI-native SDLC for development teams. Is your team short of hands rather than rules? See a senior developer for your team. AI writes most of our code. A senior reviews every change and is accountable for production. Since 2023 we have delivered 15+ production projects.
We get back to you within two business days with a few questions about your work. Then we agree on what we do first.
Shell commands that Claude Code runs on events, such as PreToolUse before a tool call or Stop when the agent wants to finish. A hook gets the event as JSON on stdin and decides with its exit code or a JSON answer.
How does a Claude Code hook block a command?
With exit code 2. Claude Code then blocks the action and passes what the hook wrote to stderr back to the agent as the reason. Exit 0 allows it. Any other code is a non-blocking error and the agent carries on.
Why does my Claude Code hook not run?
Most often a relative path that does not resolve in the session directory, stderr discarded with 2>/dev/null, or a trailing || true that rewrites the exit code. Start the path with "$CLAUDE_PROJECT_DIR" and check the hook config in CI.
What does stop_hook_active mean?
It is true when the agent is already continuing because a Stop hook blocked it. Exit early when it is true. Otherwise a check the agent cannot fix keeps blocking it forever.
Is a Stop hook enough as a quality gate?
No. It is an early warning inside the session. Run the same checks again in CI and in branch protection, where the agent cannot change them.
Pregunte a una IA sobre este artículo
La IA también tiene en cuenta nuestros otros artículos y la experiencia de proyectos reales.
Context engineering decides what an AI coding agent sees while it works. What goes into AGENTS.md or CLAUDE.md, which rules load only where they apply, how the task becomes context, and how to keep the whole thing small.
Playwright MCP gives an AI agent a real browser. How we use it so the agent checks its own change in the running app and hands back PASS or FAIL with screenshots, what it catches that unit tests miss, and when plain Playwright scripts are the better tool.
How we run Claude Code review on every pull request with one reusable GitHub Action: what the AI reviewer is told to look for, how it stayed green and silent, what it misses, and why only a person approves.
Es hora de dejar de planificar y empezar a resolver.