<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: The Agent Runbook</title>
    <description>The latest articles on DEV Community by The Agent Runbook (@agentrunbook).</description>
    <link>https://dev.to/agentrunbook</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4209797%2Fb7dac151-65c5-4866-92c4-052056cad34f.png</url>
      <title>DEV Community: The Agent Runbook</title>
      <link>https://dev.to/agentrunbook</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/agentrunbook"/>
    <language>en</language>
    <item>
      <title>3 Claude Code patterns: read-only subagents, Haiku routing, and hooks that block with exit code 2</title>
      <dc:creator>The Agent Runbook</dc:creator>
      <pubDate>Tue, 27 Oct 2026 03:27:17 +0000</pubDate>
      <link>https://dev.to/agentrunbook/3-claude-code-patterns-read-only-subagents-haiku-routing-and-hooks-that-block-with-exit-code-2-17ho</link>
      <guid>https://dev.to/agentrunbook/3-claude-code-patterns-read-only-subagents-haiku-routing-and-hooks-that-block-with-exit-code-2-17ho</guid>
      <description>&lt;p&gt;Most Claude Code setups stop at a &lt;code&gt;CLAUDE.md&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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, &lt;code&gt;claude --help&lt;/code&gt; and the docs win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: a reviewer subagent that physically cannot edit files
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;.claude/agents/reviewer.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reviewer&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Read-only&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reviewer.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;proactively&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;changes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;flag&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bugs,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;security&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;issues,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;convention&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;drift."&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonnet&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

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.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The gotcha:&lt;/strong&gt; per the docs, the &lt;code&gt;tools&lt;/code&gt; 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 &lt;code&gt;tools&lt;/code&gt; line.&lt;/p&gt;

&lt;p&gt;Two more things worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you would rather deny than allow, &lt;code&gt;disallowedTools&lt;/code&gt; removes tools from the inherited set. To get a truly read-only agent that way, list &lt;code&gt;Write, Edit, NotebookEdit, Bash&lt;/code&gt;. Deny only &lt;code&gt;Write&lt;/code&gt; and &lt;code&gt;Edit&lt;/code&gt; and the inherited Bash can still write files with a shell redirect. The allowlist above is the simpler route.&lt;/li&gt;
&lt;li&gt;Edit the file in &lt;code&gt;.claude/agents/&lt;/code&gt; directly. The &lt;code&gt;/agents&lt;/code&gt; command no longer opens a management wizard in current versions; it just prints a reminder to do that.&lt;/li&gt;
&lt;li&gt;Frontmatter field names must match exactly (&lt;code&gt;maxTurns&lt;/code&gt;, &lt;code&gt;disallowedTools&lt;/code&gt;). Claude Code ignores a field it does not recognize without reporting an error, so a typo means a silently missing guardrail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not give it &lt;code&gt;Bash&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Test it once: ask the reviewer to create a file. It should tell you it cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: put each subagent on the cheapest model that can do its job
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;model&lt;/code&gt; field on a subagent takes an alias (&lt;code&gt;sonnet&lt;/code&gt;, &lt;code&gt;opus&lt;/code&gt;, &lt;code&gt;haiku&lt;/code&gt;), a full model ID, or &lt;code&gt;inherit&lt;/code&gt;. If you omit it, the subagent follows the main conversation's model by default.&lt;/p&gt;

&lt;p&gt;A scout that only finds files and call sites does not need your strongest model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scout&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fast codebase scout. Finds files and call sites, returns a map. Use before any non-trivial change to locate the relevant code.&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;haiku&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Return only: matching files, the symbols involved, and a one-line note
for each. No analysis, no recommendations.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two benefits, and the second is often the bigger one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retrieval work runs on a cheaper model tier.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep synthesis and design decisions on the stronger model in the main thread. A practical rule: start retrieval agents on &lt;code&gt;haiku&lt;/code&gt;, and promote one only when you see it missing things. Run &lt;code&gt;/usage&lt;/code&gt; to see what a session actually cost instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: a PreToolUse hook that blocks a command and explains why
&lt;/h2&gt;

&lt;p&gt;Hooks are deterministic. The docs are direct about the difference: &lt;code&gt;CLAUDE.md&lt;/code&gt; is context, not enforced configuration, and to block an action regardless of what Claude decides you use a &lt;code&gt;PreToolUse&lt;/code&gt; hook.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;PreToolUse&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PreToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${CLAUDE_PROJECT_DIR}/.claude/hooks/guard.sh"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.claude/hooks/guard.sh&lt;/code&gt; (run &lt;code&gt;chmod +x&lt;/code&gt; on it, and install &lt;code&gt;jq&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# The hook receives the tool call as JSON on stdin.&lt;/span&gt;
&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.tool_input.command // ""'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s1"&gt;'(^|[^[:alnum:]_])rm +(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)|git +push +(.* )?(--force|-f)( |$)'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Blocked: '&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;' is destructive. Use a safer alternative (trash, --force-with-lease) and say why you need it."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This blocks &lt;code&gt;rm -rf&lt;/code&gt; and &lt;code&gt;rm -fr&lt;/code&gt;, and blocks &lt;code&gt;git push --force&lt;/code&gt; and &lt;code&gt;-f&lt;/code&gt; while still allowing &lt;code&gt;--force-with-lease&lt;/code&gt;. Run &lt;code&gt;/hooks&lt;/code&gt; inside Claude Code to confirm it loaded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The gotchas:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exit codes matter.&lt;/strong&gt; 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 &lt;code&gt;"onFailure": "block"&lt;/code&gt; next to &lt;code&gt;"type"&lt;/code&gt; 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: &lt;code&gt;echo '{"tool_input":{"command":"rm -rf /tmp/x"}}' | .claude/hooks/guard.sh; echo $?&lt;/code&gt; should print the message and &lt;code&gt;2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A regex is a speed bump, not a sandbox.&lt;/strong&gt; 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 &lt;code&gt;"permissions": { "deny": ["Bash(rm -rf *)"] }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit 2 is not the biggest hammer.&lt;/strong&gt; If a hook prints JSON with &lt;code&gt;"continue": false&lt;/code&gt;, Claude stops processing entirely. Use exit 2 for "do it differently" and keep &lt;code&gt;continue: false&lt;/code&gt; for "stop everything."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting the three together
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;.claude/&lt;/code&gt;.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;More from The Agent Runbook.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free sampler: five more plays, different from these. &lt;a href="https://agentrunbook.gumroad.com/l/sampler" rel="noopener noreferrer"&gt;https://agentrunbook.gumroad.com/l/sampler&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The Claude Code Power-User Playbook: 20 plays, $29. &lt;a href="https://agentrunbook.gumroad.com/l/playbook" rel="noopener noreferrer"&gt;https://agentrunbook.gumroad.com/l/playbook&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The Agent Runbook Kit: a ready-to-drop &lt;code&gt;.claude/&lt;/code&gt; template (agents, hooks, skills, MCP config, headless script), $49. &lt;a href="https://agentrunbook.gumroad.com/l/kit" rel="noopener noreferrer"&gt;https://agentrunbook.gumroad.com/l/kit&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One new play a week: &lt;a href="https://theagentrunbook.substack.com" rel="noopener noreferrer"&gt;https://theagentrunbook.substack.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Agent Runbook is an independent publication. It is not affiliated with or endorsed by Anthropic.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>devtools</category>
      <category>claude</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
