Skip to content

Chapter 6 of 206 min readBy Deepak GuptaPublished

Subagents and Hooks

Subagents and hooks turn Claude Code from an interactive tool into a system. Subagents isolate work so your main context stays clean. Hooks enforce checks deterministically, whether or not anyone remembers to ask.

What a subagent is

A subagent is a separate Claude instance with its own context window, system prompt, tool permissions, and optionally its own model. The main session hands it a task. It works independently and returns a summary.

The benefit is context economics. A subagent can read thirty files and run a dozen searches, and your main session receives only the conclusion. Claude Code also ships built-in helpers, such as an Explore agent for read-only codebase search, and will delegate to them on its own.

Defining a custom subagent

Subagents are markdown files with YAML frontmatter in .claude/agents/ (project) or ~/.claude/agents/ (personal). The /agents command walks you through creating one.

---
name: code-reviewer
description: Senior code reviewer. Use proactively after any non-trivial
  change, before commits, and whenever the user asks for a review.
tools: Read, Grep, Glob, Bash
model: opus
---

You are a senior engineer reviewing a change you did not write.

1. Run `git diff` to see the change. Read surrounding code for context.
2. Check correctness first, then error handling, then tests, then readability.
3. Verify that tests assert behavior, not just that code runs.
4. Report findings as Blocking, Should fix, or Nit, with file and line.
5. If the change is good, say so briefly. Do not invent problems.

Four design decisions matter:

  • Description. Like skills, it decides when Claude delegates. "Use proactively" phrasing increases automatic use.
  • Tools. Grant only what the job needs. A reviewer needs read access and maybe Bash for running tests. It does not need Edit.
  • Model. Use the strongest model for judgment-heavy work like review and security. Use a faster model for mechanical work like searching or summarizing logs.
  • Fresh eyes. A reviewer subagent has not seen the reasoning that produced the code. That is exactly why it catches what the author missed.

Invoking subagents

Claude delegates automatically when a description matches. You can also be explicit: "Have the code-reviewer agent review the last commit" or "Use subagents to investigate the three failing services in parallel." Several subagents can run at the same time.

The specialist debate

Some experienced engineers argue against many custom subagents. Their point: a python-tests subagent hides testing knowledge from the main agent, which then cannot reason about a change as a whole. They prefer putting knowledge in CLAUDE.md and letting the main agent spawn general-purpose copies of itself when it wants to parallelize.

Both views are right in different places. My rule:

  • Custom subagent when the job needs a distinct perspective or restricted permissions: security review, code review, accessibility audit, performance profiling.
  • General delegation for everything else: research, parallel exploration, bulk edits. Let the main agent decide how to split the work.

What a hook is

A hook is a shell command that Claude Code runs automatically at a lifecycle event. Hooks are deterministic. A rule in CLAUDE.md is followed most of the time; a hook runs every time.

Event Fires when Common use
SessionStart A session starts or resumes Inject today's date, branch, open tickets
UserPromptSubmit You send a prompt Add context, block prompts containing secrets
PreToolUse Before a tool runs Block dangerous commands or protected files
PostToolUse After a tool succeeds Format, lint, typecheck edited files
Notification Claude needs your attention Desktop or chat notification
Stop Claude finishes responding Run tests, verify build
SubagentStop A subagent finishes Validate subagent output
PreCompact Before compaction Save a snapshot of state
SessionEnd Session ends Log usage, clean up

How hooks receive data

A hook receives a JSON object on standard input describing the event: the tool name, its input (such as the file path being edited), and session details. Its exit code tells Claude Code what to do next. Exit code 0 means proceed. Exit code 2 means block, and whatever the hook prints to standard error is fed back to Claude so it can correct course.

That second behavior is powerful. A typecheck hook that exits 2 with the compiler errors makes Claude fix its own type errors before moving on.

A formatter and typecheck hook

In .claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-and-check.sh"
          }
        ]
      }
    ]
  }
}

And the script:

#!/usr/bin/env bash
# .claude/hooks/format-and-check.sh
set -euo pipefail
file=$(jq -r '.tool_input.file_path // empty')
[[ -z "$file" ]] && exit 0

case "$file" in
  *.ts|*.tsx)
    npx prettier --write "$file" >/dev/null 2>&1 || true
    if ! out=$(npx tsc --noEmit -p . 2>&1); then
      echo "$out" | head -40 >&2
      exit 2
    fi
    ;;
esac
exit 0

Note the head -40. Hook output that goes back to Claude consumes context, so trim it.

A guard hook

This PreToolUse hook blocks edits to protected paths:

#!/usr/bin/env bash
# .claude/hooks/protect-paths.sh
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in
  *.env*|*/migrations/*|*/generated/*)
    echo "Blocked: $file is protected. Ask the user first." >&2
    exit 2 ;;
esac
exit 0

Register it with "matcher": "Edit|Write" under PreToolUse.

Cost and debugging

Hooks that run on every edit add latency and, if they print a lot, context. Run the expensive checks, such as the full test suite, on Stop rather than on every PostToolUse. Run cheap ones, such as formatting a single file, per edit.

When a hook misbehaves, start Claude with claude --debug to see each hook's execution and output.

Warning

Pitfall: Hooks run with your user's full permissions. Treat hook scripts from plugins or other repositories as code you are about to execute, because you are.

Tip

What to do this week

  • Add a PostToolUse formatter hook for your main language.
  • Create a code-reviewer subagent with read-only tools and run it on your next change.
  • Add one PreToolUse guard for files that should never be edited without asking.

Key takeaways

  • Subagents work in their own context window and return only a summary.
  • Create custom subagents for distinct perspectives or restricted permissions, such as code and security review.
  • Hooks run every time; exit code 2 blocks the action and feeds stderr back to Claude.
  • Run cheap checks per edit and expensive ones on Stop, and keep hook output short.