There is a failure mode in long agent sessions that nobody warns you about. You start with a clear task, the agent searches for something, and now your conversation contains four thousand lines of grep output you will never read again. Everything after that point is worse, because the useful context is buried.
Subagents and hooks are the two features that address this, from opposite directions. Subagents keep noise out. Hooks put guarantees in.
Subagents: work that happens elsewhere
A subagent is a specialized assistant that runs in its own context window, with its own system prompt and its own tool access, and returns only its summary to your main conversation.
The searching happens over there. The four thousand lines stay over there. You get the answer.
That is the entire value proposition, and it is enough on its own.
The built-ins
Claude Code ships with several, used automatically:
- Explore: fast, read-only, for searching and understanding a codebase. Write and Edit are denied.
- Plan: research during plan mode, so exploration stays in a separate window while your main conversation remains read-only.
- General-purpose: complex multi-step work needing both exploration and changes.
Explore and Plan deliberately skip your CLAUDE.md and git status to keep research fast and cheap. Every other subagent loads both.
Writing your own
A subagent is a markdown file with frontmatter, in .claude/agents/ for a project or ~/.claude/agents/ for all your projects.
---
name: code-reviewer
description: Reviews code for quality and best practices. Use after writing or modifying code.
tools: Read, Glob, Grep
model: sonnet
---
You are a code reviewer. Analyze the code and give specific, actionable
feedback on quality, security, and best practices. Report findings most
severe first.
Only name and description are required. The body becomes the system prompt.
The description is what Claude uses to decide when to delegate, so it deserves more care than the body. A vague description means the subagent never fires, or fires constantly.
The fields worth knowing
tools limits what the subagent can do. Omit it and the subagent inherits everything. For anything that should only look and never touch, list read-only tools explicitly. This is enforcement, not a suggestion in a prompt.
model routes work by cost. sonnet, opus, haiku, fable, a full ID like claude-opus-5, or inherit. Defaults to inherit. Setting a lint-fixing subagent to haiku is free money.
effort overrides the session effort level while the subagent runs. Mechanical subagents want low.
skills preloads skill content into the subagent's context at startup, full body rather than just the description. Useful when a subagent always needs the same reference material.
isolation: worktree runs the subagent in a temporary git worktree, giving it an isolated copy of the repo. The worktree is cleaned up automatically if the subagent makes no changes. This is how you let something experiment without it touching your working tree.
maxTurns caps how long it can run. A useful backstop on anything autonomous.
When a subagent is the right call
The test I use: is the work verbose but the answer short?
- "Find every place we call the deprecated API" reads a lot, concludes with a list. Subagent.
- "Fix this one function" is short in and short out. Just do it in the main conversation.
The second test is enforcement. If a task must never write files, a subagent with read-only tools guarantees that in a way that "please don't edit anything" does not.
Do not reach for subagents by default. Each one starts cold and has to re-derive context you already have in the main conversation. That re-derivation is not free, and for a small task it costs more than it saves.
Hooks: conventions that cannot be forgotten
Everything above still depends on the model choosing to do the right thing. Hooks do not.
A hook is a command, HTTP endpoint, MCP tool, prompt, or subagent that runs deterministically at a defined point in the lifecycle. It is configuration, not instruction.
Configuration shape
Hooks live in settings files: ~/.claude/settings.json for all your projects, .claude/settings.json for one project and committed to the repo, .claude/settings.local.json for one project and gitignored.
Three levels of nesting: the event, a matcher group that filters when it fires, and one or more handlers that run.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh"
}
]
}
]
}
}
The events that matter most
There are many. These four cover the majority of real use:
PreToolUsefires before a tool call and can block it. The enforcement point.PostToolUsefires after a tool call succeeds. Formatting, linting, regenerating types.UserPromptSubmitfires when you submit a prompt, before Claude sees it. Inject context.SessionStartfires when a session begins or resumes. Setup.
Blocking something dangerous
A PreToolUse hook can return a permission decision. Here is one that refuses destructive shell commands:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0 # no decision; normal permission flow applies
fi
Two details that will save you time. The script must be executable (chmod +x). And these examples use jq, so it needs to be on your PATH.
The if field is a nice refinement: it filters using permission-rule syntax, so the script only spawns when the tool call actually matches. It holds exactly one rule, with no && or ||, so multiple conditions mean multiple handlers.
Auto-formatting
The most immediately useful hook most teams can add:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "yarn prettier --write --ignore-unknown $CLAUDE_FILE_PATHS"
}
]
}
]
}
}
Formatting stops being something anyone has to think about, including the agent. This is the general shape of the good use case: take a convention that currently lives in a code review comment and make it mechanical.
Where hooks can be defined
Beyond settings files, hooks can come from a plugin's hooks/hooks.json, from skill frontmatter (registered when the skill is invoked, active for the rest of the session), and from subagent frontmatter (active while that subagent runs).
Hooks from settings, managed policy, and plugins also run inside subagents. A PreToolUse hook that blocks something blocks it everywhere, which is what you want from a guardrail.
Hook entries merge across settings levels rather than replacing each other, so project hooks add to your personal ones instead of overriding them.
How they fit together
Think of it as three layers with different guarantees:
| Layer | Guarantee | Use for |
|---|---|---|
CLAUDE.md and skills | Claude is told | Conventions, procedures, context |
| Subagents | Structurally isolated | Noisy work, restricted tool access |
| Hooks | Deterministic | Rules that must not be skipped |
The mistake is trying to solve a hooks problem with prompting. If something must happen, a sentence asking for it is the wrong mechanism, however firmly worded. Anything that must always be true should be a hook. Anything that just needs to be known should be documentation.
Related: skills for reusable procedures, MCP for external systems, and models and effort levels for routing work by cost.
- #Claude Code
- #AI
- #Automation
- #Developer Tools
- #Hooks