3 Claude Code patterns: read-only subagents, Haiku routing, and hooks that block with exit code 2
Most Claude Code setups stop at a CLAUDE.md file. The bigger levers sit one layer down: subagents with locked-down tools, per-agent model choice, and hooks that enforce rules instead of asking nicely. Here are three patt
Most Claude Code setups stop at a CLAUDE.md file. The bigger levers sit one layer down: subagents with locked-down tools, per-agent model choice, and hooks that enforce rules instead of asking nicely. Here are three patterns you can paste into a repo today, each with the gotcha that usually bites.
Everything below was checked against the official Claude Code docs (code.claude.com/docs) in October 2026. The CLI changes quickly, so if a field has moved, claude --help and the docs win.
Pattern 1: a reviewer subagent that physically cannot edit files
A code reviewer that can edit will "helpfully" fix the thing you wanted flagged. A system prompt saying "do not edit" is a request. A tool allowlist is a constraint.
Create .claude/agents/reviewer.md:
---
name: reviewer
description: "Read-only code reviewer. Use proactively after code changes to flag bugs, security issues, and convention drift."
tools: Read, Grep, Glob
model: sonnet
---
You are a senior reviewer. Report findings by severity with file:line
citations. You have no write tools. Return a findings list for the main
agent to act on; do not try to fix anything yourself.
The gotcha: per the docs, the tools field is an allowlist, and a subagent "inherits every tool available to subagents if omitted." That includes Edit and Write, and Bash. The classic mistake is a carefully worded read-only prompt with no tools line.
Two more things worth knowing:
- If you would rather deny than allow,
disallowedToolsremoves tools from the inherited set. To get a truly read-only agent that way, listWrite, Edit, NotebookEdit, Bash. Deny onlyWriteandEditand the inherited Bash can still write files with a shell redirect. The allowlist above is the simpler route. - Edit the file in
.claude/agents/directly. The/agentscommand no longer opens a management wizard in current versions; it just prints a reminder to do that. - Frontmatter field names must match exactly (
maxTurns,disallowedTools). Claude Code ignores a field it does not recognize without reporting an error, so a typo means a silently missing guardrail.
Do not give it Bash and still call it read-only. A shell can write files with a redirect. If the reviewer needs to run commands, give it Bash and add deny rules for the destructive patterns.
Test it once: ask the reviewer to create a file. It should tell you it cannot.
Pattern 2: put each subagent on the cheapest model that can do its job
The model field on a subagent takes an alias (sonnet, opus, haiku), a full model ID, or inherit. If you omit it, the subagent follows the main conversation's model by default.
A scout that only finds files and call sites does not need your strongest model:
---
name: scout
description: Fast codebase scout. Finds files and call sites, returns a map. Use before any non-trivial change to locate the relevant code.
tools: Read, Grep, Glob
model: haiku
---
Return only: matching files, the symbols involved, and a one-line note
for each. No analysis, no recommendations.
Two benefits, and the second is often the bigger one:
- Retrieval work runs on a cheaper model tier.
- The subagent works in its own context window. Thousands of lines of search output never enter your main conversation; you get the summary, not the noise.
Keep synthesis and design decisions on the stronger model in the main thread. A practical rule: start retrieval agents on haiku, and promote one only when you see it missing things. Run /usage to see what a session actually cost instead of guessing.
Pattern 3: a PreToolUse hook that blocks a command and explains why
Hooks are deterministic. The docs are direct about the difference: CLAUDE.md is context, not enforced configuration, and to block an action regardless of what Claude decides you use a PreToolUse hook.
A PreToolUse hook that exits with code 2 blocks the tool call, and Claude sees your stderr message as the reason. So the model can correct course instead of just failing.
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard.sh"
}
]
}
]
}
}
.claude/hooks/guard.sh (run chmod +x on it, and install jq):
#!/usr/bin/env bash
# The hook receives the tool call as JSON on stdin.
cmd=$(jq -r '.tool_input.command // ""')
if printf '%s' "$cmd" | grep -qE '(^|[^[:alnum:]_])rm +(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)|git +push +(.* )?(--force|-f)( |$)'; then
echo "Blocked: '$cmd' is destructive. Use a safer alternative (trash, --force-with-lease) and say why you need it." >&2
exit 2
fi
exit 0
This blocks rm -rf and rm -fr, and blocks git push --force and -f while still allowing --force-with-lease. Run /hooks inside Claude Code to confirm it loaded.
The gotchas:
-
Exit codes matter. Exit 0 lets the call continue through the normal permission flow. Exit 2 blocks. Any other non-zero code, including a crash that exits 1, is a non-blocking error and the action goes ahead. A guard that fails open is worse than no guard. On Claude Code v2.1.295 and later, add
"onFailure": "block"next to"type"in the hook entry so a crash, timeout or missing script blocks the call like exit 2 does. Either way, test yours by piping fake input in by hand:echo '{"tool_input":{"command":"rm -rf /tmp/x"}}' | .claude/hooks/guard.sh; echo $?should print the message and2. -
A regex is a speed bump, not a sandbox. A determined or confused agent can phrase a destructive command a way your pattern misses. For hard denies, pair the hook with a permission deny rule such as
"permissions": { "deny": ["Bash(rm -rf *)"] }. -
Exit 2 is not the biggest hammer. If a hook prints JSON with
"continue": false, Claude stops processing entirely. Use exit 2 for "do it differently" and keepcontinue: falsefor "stop everything."
Putting the three together
The reviewer cannot edit, the scout is cheap and keeps your context clean, and the guard hook holds even when a prompt is ignored. None of this needs a framework: three small files in .claude/.
More from The Agent Runbook. These three patterns are free and complete as written. I also sell material that goes further. This is my own work, so weigh it accordingly.
- Free sampler: five more plays, different from these. https://agentrunbook.gumroad.com/l/sampler
- The Claude Code Power-User Playbook: 20 plays, $29. https://agentrunbook.gumroad.com/l/playbook
- The Agent Runbook Kit: a ready-to-drop
.claude/template (agents, hooks, skills, MCP config, headless script), $49. https://agentrunbook.gumroad.com/l/kitOne new play a week: https://theagentrunbook.substack.com
The Agent Runbook is an independent publication. It is not affiliated with or endorsed by Anthropic.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes โ full credit and traffic to the original publisher.