Claude Opus 4.7 Is Ignoring Stop Hooks — Detect and Work Around It
Claude Opus 4.7 is silently ignoring your stop hooks. Not throwing an error, not warning you — just skipping them entirely. Which means your agent might be executing commands you explicitly told it to halt before running. Developers have been reproducing this consistently, with around 80 comments’ worth of confirming reports on a busy thread, and as of this writing there’s no confirmed fix in the wild.
This post shows you how to reproduce the failure, verify whether your environment is affected, and ship two workarounds that hold up in practice: a CLAUDE.md guard and a wrapper script that validates hook execution before the agent proceeds.
What you need
You’ll need Claude Code installed and configured, a shell you can run scripts from, and a basic ~/.claude/settings.json or project-level config where you’ve defined hooks. If you haven’t touched Claude Code hooks before, they’re lifecycle callbacks that let you intercept the agent before it runs certain tool calls — stop hooks specifically fire before a final action is taken.
Step 1: Write a minimal stop-hook test harness
Before debugging anything, you need a reproducible baseline. Create a test project with a deliberate stop hook that writes to a sentinel file. If the hook ran, the file exists. If it didn’t, you have a silent skip.
mkdir hook-test && cd hook-test
# Create a sentinel-writing stop hook
cat > stop_hook.sh << 'EOF'
#!/bin/bash
# Stop hook: write a sentinel so we know we were called
echo "hook_ran=true ts=$(date +%s)" >> /tmp/hook_sentinel.log
exit 0
EOF
chmod +x stop_hook.sh
Now wire it into your Claude Code project config:
{
"hooks": {
"stop": "./stop_hook.sh"
}
}
Run a quick Claude Code session that terminates normally. Then check the sentinel:
cat /tmp/hook_sentinel.log
If the file is empty or missing, your stop hook was skipped. On Claude Opus 4.7, a meaningful number of users are seeing exactly this — the hook is configured, Claude Code acknowledges it in the session startup log, and then it never fires.
Step 2: Confirm the regression with a failing assertion
A log file you manually check is fine for a one-off. For CI or team setups, you want this to actually fail loudly. Wrap the session in a shell script that treats a missing sentinel as a hard error:
cat > run_with_hook_check.sh << 'EOF'
#!/bin/bash
set -euo pipefail
SENTINEL="/tmp/hook_sentinel.log"
# Clear previous sentinel
rm -f "$SENTINEL"
# Run your Claude Code command here
# Replace the echo with: claude --no-interactive "your task"
echo "[placeholder: claude session would run here]"
# Assert the hook fired
if [[ ! -f "$SENTINEL" ]]; then
echo "FAIL: stop hook did not execute. Claude Opus 4.7 regression confirmed." >&2
exit 1
fi
echo "OK: stop hook ran as expected."
grep "hook_ran" "$SENTINEL"
EOF
chmod +x run_with_hook_check.sh
This is your canary. Run it after every Claude Code update — when the sentinel appears reliably again, the regression is patched in your version.
Step 3: Ship two workarounds
The sentinel tells you the problem is real. Here are two things you can actually deploy while waiting for a fix.
Workaround 1 — CLAUDE.md guard
Claude Code reads CLAUDE.md at the start of every session. You can use it to instruct the model to explicitly confirm hook-gated actions before taking them, giving you a human-readable audit trail even when the hook machinery misfires.
<!-- CLAUDE.md -->
## Stop hook policy (temporary — regression workaround)
Before executing any final destructive or irreversible action (file deletion,
deployments, database writes), output the exact string:
STOP_HOOK_CONFIRM: <action description>
Then pause and wait for explicit user approval.
This is a temporary safeguard because Claude Opus 4.7 has a known regression
where stop hooks may not fire. Remove this section when the fix is confirmed.
This doesn’t fix the underlying hook skip, but it pushes the safety gate into the conversation layer where you can see it. The tradeoff: it adds a round-trip on every guarded action, which gets noisy for long autonomous sessions.
Workaround 2 — wrapper script with pre-flight hook validation
If you’re running Claude Code non-interactively (CI pipelines, automated scripts), the CLAUDE.md approach doesn’t help. Instead, run the hook yourself before handing off to Claude:
cat > safe_claude.sh << 'EOF'
#!/bin/bash
set -euo pipefail
HOOK_SCRIPT="${CLAUDE_STOP_HOOK:-./stop_hook.sh}"
SENTINEL="/tmp/hook_sentinel.log"
# Pre-flight: verify hook is executable
if [[ ! -x "$HOOK_SCRIPT" ]]; then
echo "ERROR: stop hook '$HOOK_SCRIPT' is not executable." >&2
exit 1
fi
# Pre-flight: dry-run the hook to confirm it works at all
rm -f "$SENTINEL"
"$HOOK_SCRIPT"
if [[ ! -f "$SENTINEL" ]]; then
echo "ERROR: stop hook ran but produced no sentinel. Check the hook script." >&2
exit 1
fi
echo "Pre-flight passed: stop hook is functional."
# Clear sentinel before the real session
rm -f "$SENTINEL"
# Hand off to Claude Code — pass all args through
# Replace with your actual claude invocation:
claude "$@"
# Post-flight: check hook fired during the real session
if [[ ! -f "$SENTINEL" ]]; then
echo "WARN: stop hook did NOT fire during Claude session (4.7 regression)." >&2
echo "Review session output manually before treating results as safe." >&2
exit 2
fi
echo "Session complete. Stop hook fired as expected."
EOF
chmod +x safe_claude.sh
Exit code 2 here is intentional — it distinguishes “hook regression detected” from other failures, so your CI can alert on it specifically rather than treating it as a generic crash.
Where this breaks
Neither workaround is airtight. The CLAUDE.md guard relies on model instruction-following, which isn’t a hard system constraint — it’s a best-effort layer on top of a broken mechanism. A sufficiently complex session context can push the instruction below the attention horizon.
The wrapper script is more reliable for non-interactive use, but it only checks whether the hook fired, not whether it blocked the right things. If your stop hook is supposed to conditionally abort based on context (e.g., “don’t deploy on Fridays”), the wrapper can’t replicate that logic cheaply — you’d need to extract the condition into a standalone script and call it twice.
Also worth noting: based on what’s been shared publicly, no patch specifically targeting the stop hook skip behavior has been confirmed. Anthropic published a postmortem covering a related set of regressions around the same time this surfaced, but that document doesn’t appear to address this specific lifecycle issue based on what’s been reported. Keep monitoring official Claude Code release notes before removing these guards.
Next steps
Pin this in your team’s Claude Code setup docs until the regression is officially patched. The sentinel pattern is cheap enough to leave in permanently — a hook that proves it ran costs almost nothing, and a few lines in CI that catch a silent skip could save you a very bad deploy. When you see the sentinel reliably appearing again across Claude versions, you can retire the workarounds and treat the whole thing as a regression test that finally passed.
← Back to blog