The dotfiles piece from May 22 named hooks as one component of a personal AI stack and moved on. They deserve more than a passing mention. Hooks are the primitive that turns taste into code — the editor's auto-format-on-save for AI work, run on the agent's actions instead of yours.
Anthropic's hooks documentation lists eight event types. Most published examples wire up one of them, demo a tiny safety check, and stop. The real payoff is in pairing the right hook to the right invariant for your work. Below are the five hooks I run, with the actual invariants they enforce.
Hook 1: PreToolUse — Guard the Destructive Commands
PreToolUse fires before any tool call, with the tool name and arguments. The hook can approve, deny, or rewrite. The high-yield use is denying classes of commands you never want the agent to run unattended — rm -rf, git reset --hard, git push --force to main, gcloud auth revoke, kubectl delete against production, anything with a flag that turns "ask first" into "do it now."
The shape of the hook is a small shell script that reads the tool call from stdin, pattern-matches on dangerous combinations, and exits with a non-zero status to deny. Examples in production:
- Deny
rm -rfagainst any path outside/tmp. - Deny
git push --forcetomainormasterregardless of remote. - Deny
--no-verifyongit commitunlessALLOW_NO_VERIFY=1is set explicitly in the session env. - Deny any
gh prcommand with--adminor auto-merge flags.
The hook is a safety net, not a configuration. The agent already knows not to do these things. The hook catches the case where it almost did anyway.
Hook 2: PostToolUse — Auto-Lint Every Write
PostToolUse fires after a tool call completes, with the tool name, arguments, and result. The hook reads, runs whatever side effect you want, and returns. For file writes, this is where you run linting, formatting, type checking, and any project-specific guard.
The shape: a hook that filters for tool_name == "Write" || tool_name == "Edit", then runs the relevant linter against the file path that was written. In my setup this means prettier --write for JS/TS, ruff check --fix for Python, shellcheck for bash scripts. The hook does not block the agent's next action — by the time PostToolUse runs, the write has already happened. It does silently fix what it can and report what it cannot.
The win is consistency. Every file the agent writes ends up formatted the same way as every file I write. The agent's "I wrote this fast and ugly" output is indistinguishable from a deliberate commit.
Hook 3: Stop — Session-End Auto-Commit with Stale-Lock Defense
Stop fires when the agent finishes responding. This is the hook most people skip and where the highest payoff lives.
My Stop hook runs git add -A && git commit -m "<auto-commit message>" against any repo I have configured in ~/.claude/hooks/session-end-commit.sh. Every Claude Code session ends with a snapshot. I can always see what changed in the session because it is a real commit in real history.
The interesting part is the stale-lock defense. On May 15 I discovered the session-end hook had been silently failing for eleven days against a 0-byte .git/index.lock file left behind by a crashed git process on May 3. The fix was a five-line block that checks for the lock file, verifies its mtime is older than five minutes, verifies no process holds it via lsof, and only then removes it. Live process: preserved. Stale lock: cleaned. Healthy auto-commits complete in under a second, so the five-minute threshold cannot race a real concurrent run.
The lesson generalizes. Hooks accumulate edge cases. The first version of any hook works on the happy path. The version that survives a year of daily use is the one that handles the failure modes you discovered along the way.
Hook 4: SessionStart — Auto-Load Context
SessionStart fires when a new Claude Code session opens. This is where you pre-load the context your work needs every single time. The point is removing the recurring "read these three files first" prompt from your routine.
Mine loads:
- The current state of the project's
SESSION-STATE.md— what's in progress, what's blocked, what's next. - The relevant agent context file from
~/nexus/agents/based on the directory I am working in. - A condensed log of yesterday's work — the last day's commits across the active projects.
- The active tasks from the queue file if one exists in the project.
The hook returns text that gets injected into the session as a system reminder, the same shape as the auto-memory mechanism. By the time I type my first prompt, the agent already knows where I left off, what I am working on, and what is on the next-action list.
This is the hook that turns Claude Code from a stateless assistant into a continuous-with-me collaborator without changing anything about the underlying model.
Hook 5: UserPromptSubmit — Prompt-Level Guards
UserPromptSubmit fires before the agent sees your prompt. The hook can rewrite the prompt, append context, or block submission. Most uses I see in the wild are filters for safety words, which is the boring case. The interesting cases are project-specific guards.
Examples I run:
- If the prompt contains "ship" or "publish" or "deploy" against the content-engine repo, the hook injects a reminder of the
--shipflag protection and the manual-publish pattern. - If the prompt is a single command verb against a production directory (
run,start,deploy), the hook injects the relevant CLAUDE.md section that explains the safer alternative. - If the prompt mentions a person whose name appears in
~/nexus/agents/personal-contacts.md, the hook injects the relevant context — old college roommate, current employer relationship, the prior interaction — so the agent does not treat the message as cold-outreach.
The hook does not block the prompt. It supplements it. The agent sees a richer version of what I typed, with context I would have had to remember to include otherwise.
What Hooks Are Actually For
The pattern across all five is the same. The hook encodes a rule I will not enforce manually because I will forget. The forcing function is that the hook runs every single time, regardless of whether I remembered to invoke it.
This is the same reason auto-format-on-save changed how teams write code in the 2010s. Not because format-on-save is technically interesting. Because the alternative — remembering to run the formatter every time — fails reliably enough that the team's code drifts from the style guide within a quarter.
Hooks for AI work are the same primitive at a different layer. They are how individual operators encode the rules the institutional plan is still drafting. The team that ships with the same lint, the same auto-commit, the same context-loading, every single Claude Code session — across every engineer — has built something the enterprise rollout document will be eighteen months catching up to.
Build yours. The five above are a working starting point.