Appearance
Claude Code hooks
Project lifecycle hooks for Claude Code sessions. They mirror Grok agent hooks so both agents run the same quality bar. Hooks live under .claude/hooks/, registered in .claude/settings.json, and are version-controlled with the repo.
Layout
.claude/
settings.json # registers PreToolUse + PostToolUse + Stop + PreCompact + SessionStart + SubagentStop hooks
hooks/
pre-tool-safety.sh # PreToolUse (matcher: Bash)
block-generated-edits.sh # PreToolUse (matcher: Edit|Write|MultiEdit)
format-on-edit.sh # PostToolUse
post-agent-quality.sh # Stop
pre-compact-snapshot.sh # PreCompact
post-compact-check.sh # SessionStart (matcher: compact)
audit-subagent.sh # SubagentStop
logs/
subagent-audit.jsonl # local, gitignored — written by audit-subagent.shPreToolUse — shell safety and deploy approval
When: a Bash tool call is about to run.
Hard deny (no prompt): these patterns are always blocked via permissionDecision: "deny" and the command does not run:
rm -rf /orrm -rf ~(and similar)git push --force/-fgit reset --hardgit clean -f/-fdmkfs,dd if=, classic fork-bomb
Explicit permission required: these return permissionDecision: "ask", which escalates to Claude Code's normal Allow/Deny permission prompt instead of running silently:
| Kind | Example commands |
|---|---|
| Production deploy (Vercel) | pnpm run deploy, vercel --prod, vercel deploy … |
| Nested app / CMS deploy | e.g. pnpm run studio:deploy, package deploy scripts |
| Data seed / write scripts | e.g. pnpm run *:seed-*, node scripts/seed-… |
One-off override: prefix the command with ALLOW_DEPLOY=1 to skip the ask decision for that single command.
Timeout: 15 seconds.
Unlike Grok's equivalent hook (a macOS osascript Allow/Deny dialog, fails closed off-GUI), this one reuses Claude Code's built-in permission-prompt UI via permissionDecision: "ask" — no GUI dependency, works the same over SSH.
Manual equivalent: keep a shared pre-tool-safety.sh pattern list (e.g. under .grok/hooks/bin/) if you want to check a command by hand before running it.
PreToolUse — block generated-file edits
When: an Edit, Write, or MultiEdit tool call is about to run.
Hard deny (no prompt, no override): permissionDecision: "deny" when tool_input.file_path matches paths that should never be hand-edited, for example:
- Generated type dumps (e.g. from a schema/typegen pipeline)
- Lockfiles (
pnpm-lock.yamland nested package lockfiles) - Build output (
.next/**,dist/**,studio/dist/**, …) node_modules/**
These files should never be hand-edited, so unlike the deploy/seed commands above there's no ask escalation or ALLOW_DEPLOY-style override — the underlying command (pnpm install, typegen, a build) is always the right fix.
This complements format-on-edit.sh below, which only skips formatting these same paths; this hook stops the edit from happening at all.
Timeout: 15 seconds.
Claude Code-only — no Grok mirror by default (add one under .grok/hooks/ if Grok sessions need the same protection).
Stop — post-agent quality gate
When: the main agent finishes responding (every turn end — Claude Code's Stop event has no equivalent of Grok's end_turn vs. session-end distinction, so this runs on every stop).
Runs a project script chain, typically:
pnpm run post-agent— Prettier write → ESLint →tsc --noEmit→ unit tests- A changed-files dead-code / complexity audit vs merge-base (e.g. Fallow with a known-debt baseline)
On failure: stdout is a JSON {"decision": "block", "reason": "..."} object with truncated log output, which blocks the stop and feeds the failures back to the model so it can fix issues and finish again.
Timeout: 600 seconds (format + lint + types + tests can take a while on a cold cache).
Manual equivalent: run the same scripts locally (pnpm run post-agent and your audit command).
PostToolUse — format on edit
When: an Edit, Write, or MultiEdit tool call completes.
Runs: prettier --write on that file only (via pnpm exec prettier), reading the path from tool_input.file_path.
Skips:
- Missing paths
- Non-source extensions
- Vendor / build / generated paths (same set as the generated-file blocklist)
Timeout: 30 seconds. Failures are non-blocking (stderr note only, does not stop the agent).
PreCompact — pre-compaction snapshot
When: just before context compaction runs, whether triggered manually (/compact) or automatically when the context window fills up.
Runs: git branch --show-current, git status --short, git diff --stat (unstaged + staged), git log -5 --oneline, printed to stdout (capped at ~4000 chars). This has no way to block or edit compaction — it exists purely so the branch, changed files, and recent commits survive into the compacted summary instead of depending on someone having run /handoff first.
Timeout: 15 seconds.
Manual equivalent: the /handoff skill (richer — includes in-progress narrative, not just raw git output), or plain git status --short && git diff --stat && git log -5 --oneline.
SessionStart (compact) — post-compaction check
When: a session resumes immediately after a compaction. No-ops for the other SessionStart sources (startup, resume, clear) — checked via the source field on stdin.
Runs: the same git snapshot as PreCompact, injected via hookSpecificOutput.additionalContext so it lands in context as ground truth to check the just-restored summary against, rather than a bare instruction to go verify. Also capped at ~4000 chars.
Timeout: 15 seconds.
Manual equivalent: run /orient right after a compaction to get the same read-only reality check.
SubagentStop — background subagent audit log
When: a subagent (a Task tool call — e.g. an Agent or Explore invocation) finishes.
Runs: best-effort extraction from the subagent's transcript (transcript_path from stdin), appending one JSON line to .claude/logs/subagent-audit.jsonl (local-only, gitignored — never committed):
sessionId— from the hook payloadagentName— scanned from the transcript for asubagentType/agentType-shaped field;"unknown"if not foundtoolsUsed— distinct tool names fromtool_useblocks in the transcriptsummary— the subagent's last assistant text block, truncated to ~500 chars
Schema caveat: Claude Code's SubagentStop payload is documented to carry session_id + transcript_path (same shape as Stop), but whether a subagent name/type is present wasn't always verifiable against the installed CLI. agentName extraction is therefore best-effort, not guaranteed.
Failure posture: always exits 0 — a missing/unreadable transcript, a parse error, or a failed log write never blocks the subagent from finishing.
Timeout: 30 seconds.
Claude Code-only — no Grok mirror by default.
Day-to-day flow
What is not hooked
Same exclusions as Grok hooks — long-running servers (dev, nested app servers, docs dev, test watch), slow/heavy commands (e2e suite, full production build), silent auto-fix on Stop (prefer explicit fixes), and interactive/human-driven flows.
Secret-exposing commands (cat .env, printenv, etc.) aren't covered by the shell-safety pattern list — those are gated separately by permission rules (deny entries in .claude/settings.json). The permission ask entries for vercel * / pnpm run deploy overlap with the hook's own ask decision for the same commands; the hook exists so prefix variations and chained commands (e.g. cd nested-app && pnpm deploy) are still caught, not just the literal permission-rule prefixes.
PreCompact and SessionStart have no Grok mirror — Grok CLI doesn't expose a compaction lifecycle hook. Generated-file block and subagent audit are likewise Claude Code-only unless you add Grok equivalents.
Troubleshooting
| Symptom | Check |
|---|---|
| Hooks never run | Confirm .claude/settings.json is present and the session was started in the repo root |
| Stop always blocks | Run post-agent and the audit script locally; fix failures |
| Format-on-edit no-op | File extension not in allowlist, or path is generated/vendor |
| Deploy/seed command hangs | It's waiting on the Allow/Deny permission prompt from permissionDecision: "ask" |
| Edit to generated file denied | Expected — regenerate via the proper script instead of hand-editing |
| Subagent audit log empty | Check the gitignored log path under .claude/logs/ |
Related
- Claude Code permissions — allow/ask/deny rules, complementary to these hooks
- Claude Code session management — manual
/orientand/handoffskills - Grok agent hooks — equivalent setup for Grok CLI sessions
- Architecture decision records — the "why" behind non-obvious project choices