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, disallowedTools removes tools from the inherited set. To get a truly read-only agent that way, list Write, Edit, NotebookEdit, Bash. Deny only Write and Edit and 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 /agents command 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:

  1. Retrieval work runs on a cheaper model tier.
  2. 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 and 2.
  • 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 keep continue: false for "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.

One new play a week: https://theagentrunbook.substack.com

The Agent Runbook is an independent publication. It is not affiliated with or endorsed by Anthropic.