Claude Code hooks: give your agent the right context at the right moment (with examples)
By Sebastián Téllez · Last updated: October 10, 2026
Short answer: hooks are commands Claude Code runs at fixed points of a session, such as when it starts, when you send a message or right before a tool runs. Unlike CLAUDE.md, which Claude treats as context it may or may not follow, a hook always runs. Use SessionStart and UserPromptSubmit to put the critical information in front of the agent, and PreToolUse to block an action no matter what the agent decides.
Why CLAUDE.md isn't enough
Anthropic's documentation says it directly: Claude treats CLAUDE.md and auto memory as context, not as enforced configuration. To block an action regardless of what Claude decides, it recommends a PreToolUse hook.
The hooks guide describes them as deterministic control: certain actions always happen instead of relying on the model to choose them. That is the difference between telling the agent something once and making sure it has it every time it matters.
The events that matter when the agent decides
Claude Code has more than thirty hook events. For getting the right information to the agent at the right moment, these are the useful ones:
- SessionStart: when a session starts or resumes (its input says whether it was startup, resume, clear, compact or fork). Whatever the hook prints is added to the context.
- UserPromptSubmit: every time you send a message, before Claude processes it. The hook receives the text of your message in the prompt field and can add context for that specific message.
- PreToolUse: right before a tool runs (Bash, Edit, Write, an MCP tool…). The hook receives tool_name and tool_input and can block the call.
- PreCompact: before the context is compacted, useful to save what should not be lost.
- Stop: when Claude finishes responding, useful to record what happened.
Where hooks are configured
- ~/.claude/settings.json: all your projects, on your machine.
- .claude/settings.json: one project, shared with your team through the repository.
- .claude/settings.local.json: one project, not shared.
- Organization-managed settings, plugins, skills and subagents can also define hooks.
Entries from the different levels are merged, not replaced. The /hooks command shows every configured hook and where it comes from; to add or change one, edit the settings JSON.
Example 1: the critical context at session start
A project keeps its decisions in .claude/decisions.md. This hook loads them at the start of every session, including after a compaction:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{ "type": "command", "command": "cat \"$CLAUDE_PROJECT_DIR/.claude/decisions.md\"" }
]
}
]
}
}Plain text printed by a SessionStart hook is added to Claude’s context. If you prefer structured output, return JSON with hookSpecificOutput.additionalContext. Either way, the documentation caps each injection at 10,000 characters: beyond that, Claude only gets a file path and a preview, so put the essentials first.
Example 2: context for the message you just sent
Loading everything at the start does not scale. A UserPromptSubmit hook can look at the message and add only what applies. Here, if the message mentions a deploy, it adds the deploy rules:
#!/usr/bin/env bash
prompt=$(jq -r '.prompt')
if grep -qiE 'deploy|release|production' <<<"$prompt"; then
cat "$CLAUDE_PROJECT_DIR/.claude/context/deploy-rules.md"
fi
exit 0Register it under UserPromptSubmit the same way as Example 1. Keep it fast: on this event the default timeout is 30 seconds, and the agent waits for it.
Example 3: block the costly action
Illustrative example: a team decided that nobody force-pushes, because it once rewrote shared history. Instead of hoping the agent remembers, a PreToolUse hook stops it:
#!/usr/bin/env bash
cmd=$(jq -r '.tool_input.command // empty')
if grep -qE 'git push.*(--force|-f( |$))' <<<"$cmd"; then
echo "Blocked: no force-push in this repository (it rewrote shared history once)." >&2
exit 2
fi
exit 0{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/no-force-push.sh\"" }
]
}
]
}
}Exit code 2 blocks the call and Claude sees the stderr message as the reason, so it can look for another way. Any other non-zero code does not block: the action goes ahead and only a hook error is shown. The structured alternative is to exit 0 with hookSpecificOutput.permissionDecision set to "deny" and a permissionDecisionReason.
Where the information comes from
A file in the repository works for one project on one machine. It falls short when the decision was made in another tool (ChatGPT, Cursor), on another machine or in another project, or when it stops being true and nobody updates the file.
NEXUS is a source of that information for all your agents: what any of them records (decisions, business rules, incidents and how they were fixed) is delivered to the others. In Claude Code it arrives through hooks: one at session start with the brief (active goals, pending tasks), one on every message with what is relevant to that message, and one at the end of the turn that saves what happened. They are installed only if you approve them: ask your agent for the get_setup_kit tool after connecting NEXUS.
To be precise about what it does today: NEXUS’s hooks deliver context; they do not block actions. For blocking, write your own PreToolUse hook like the one in Example 3.
To connect it: create a free account at https://nexus.eblas.link/en/signup, then run claude mcp add --transport http nexus-agi https://nexus.eblas.link/mcp and authorize with /mcp.
Before you install any hook
- Hooks run commands on your machine with your permissions. Read every script before adding it, including ours.
- The example scripts need jq to read the JSON the hook receives, and must be executable: chmod +x .claude/hooks/*.sh.
- Keep them fast and fail-safe: if the source of information is down, the hook should print nothing and exit 0, not stop your work.
- Only exit code 2 blocks. Use it on purpose, and always explain the reason in stderr.
What are Claude Code hooks?
Commands, HTTP calls or prompts that Claude Code runs automatically at specific points of a session, such as session start, each message or before a tool runs. They are configured in the settings JSON and always run, unlike instructions in CLAUDE.md.
How do I add context with a hook?
With a SessionStart or UserPromptSubmit hook: plain text it prints is added to Claude’s context, or it can return JSON with hookSpecificOutput.additionalContext. Each injection is capped at 10,000 characters.
How do I block a command with a hook?
With a PreToolUse hook that exits with code 2 and writes the reason to stderr; Claude sees that reason. Alternatively, exit 0 with JSON setting hookSpecificOutput.permissionDecision to "deny".
Does NEXUS replace CLAUDE.md?
No. CLAUDE.md is still the place for stable project rules. NEXUS adds what comes from your other agents, machines and projects, and delivers it through hooks when it is relevant.