Claude Code Hooks
Hooks turn good intentions into guarantees. Here's how they work, which ones are worth setting up, and how to use one to load your team's shared context on every session.
Most of what you tell an agent is a request — the model can follow it or not. Hooks are different: they're code Claude Code runs automatically at fixed points, every time. If something must happen — a file gets formatted, a protected path stays protected, shared context gets loaded — a hook is how you guarantee it.
What a hook is
A hook is a shell command bound to a lifecycle event. When the event fires, Claude Code runs your command, optionally passes it details about what's happening, and uses the result to decide what to do next. Because it's just a shell command, a hook can do anything your shell can: run a formatter, call a linter, hit an API, print context, reject an action.
The lifecycle events
| Event | Fires when | Common use |
|---|---|---|
| SessionStart | A new session begins | Inject shared context, print project state |
| PreToolUse | Before the agent runs a tool | Guardrails — block edits to protected paths or risky commands |
| PostToolUse | After a tool runs | Format on edit, run a check, log the change |
| Stop | The agent finishes responding | Notifications, cleanup, summary |
The exact event set evolves with Claude Code, but the shape is stable: something happens, your hook runs, its exit code and output steer the agent.
Hooks worth setting up
- Format on edit (PostToolUse). Run your formatter after every file write so the agent's output always matches your style — no reminders needed.
- Protect critical paths (PreToolUse). Reject edits to lockfiles, generated code, or infrastructure config unless explicitly intended.
- Guard dangerous commands (PreToolUse). Block force-push, destructive deletes, or prod operations behind a confirmation.
- Notify on completion (Stop). Ping yourself when a long-running task finishes.
The context hook: load shared knowledge on every session
The single highest-value hook for a team is a SessionStart hook
that loads your shared context. Instead of relying on each
developer's local CLAUDE.md — which drifts — you print the
team's decisions, conventions, and ownership at the top of every
session, so the agent always reasons from the same source of truth.
Conceptually, the hook just emits your context to stdout:
# .claude/settings.json (SessionStart hook, simplified)
{
"hooks": {
"SessionStart": [
{ "command": "first-tree inject --for $CLAUDE_PROJECT_DIR" }
]
}
} The command reads the relevant nodes from your context tree and prints them; Claude Code folds that into the session. Every agent — yours and your teammates' — starts already knowing what your team has decided.
This is how First-Tree plugs into Claude Code.
First-Tree is an open-source platform for engineering teams shipping with humans and agents side by side: agents chat alongside you in shared threads, GitHub issues and PRs become the queue the right agent picks up, and a living context tree of decisions, designs, and ownership keeps every agent on the same page. The hook is the on-ramp for that last pillar — a SessionStart hook injects the right nodes into each Claude Code session, so shared memory loads automatically instead of living in per-developer files that fall out of sync. See Claude Code best practices for how this fits the rest of a team setup.
Gotchas
- Hooks run every time — keep them fast. A slow SessionStart or PostToolUse hook taxes every interaction. Cache where you can.
- Mind blocking vs non-blocking. A PreToolUse hook that blocks should fail clearly; a formatting hook shouldn't halt the agent on a non-fatal warning.
- Exit codes matter. The hook's exit status is the signal Claude Code reads. Return success/failure deliberately.
Hooks pair naturally with MCP servers (for tool access) and subagents (for scoped work). Together they're how you run Claude Code as a team rather than a collection of disconnected sessions.