Build This Now
Build This Now
Keyboard ShortcutsStatus Line Guide
CLAUDE.md, Skills, Subagents, Hooks: When to Use WhichClaude Code Subagents: The 3 to 5 Agent Sweet SpotCLAUDE.md Best Practices: The File That Makes Claude Code ReliableHow to Fix Claude Code Running Out of Context
speedy_devvkoen_salo
Blog/Toolkit/Hooks/Self-Validating Claude Code Agents

Self-Validating Claude Code Agents

Self-validating Claude Code agents: wire PostToolUse lint hooks, Stop hooks, and read-only reviewer sub-agents into agent definitions so bad output never ships.

Stop configuring. Start building.

SaaS builder templates with AI orchestration.

See what we build for companies →
speedy_devvWritten by speedy_devvPublished Jan 18, 2026Toolkit hubHooks index

Problem: An agent hands back work that looks fine at a glance. Then the linter screams, an export is missing, and a whole file was skipped. You only spot it during review, twenty minutes after the run finished.

Quick Win: Drop a PostToolUse hook straight into the agent definition. Every file this agent writes goes through the linter before you ever see it:

# .claude/agents/frontend-builder.md
---
name: frontend-builder
description: Build React components with automatic quality checks
model: sonnet
hooks:
  PostToolUse:
    - matcher: "Write|Edit"
      hooks:
        - type: command
          command: 'npx eslint --fix "$CLAUDE_TOOL_INPUT_FILE_PATH" && npx prettier --write "$CLAUDE_TOOL_INPUT_FILE_PATH"'
---
You are a frontend builder agent. Create React components following
the project's established patterns. Every file you write is automatically
linted and formatted by your embedded hooks.

Unlinted code is no longer an option for this agent. The check belongs to who it is, not something you bolt on afterwards.

There are three tiers to a self-validating agent. Each one catches a different class of bug, and they stack cleanly.

Micro is per tool call. A PostToolUse hook on the agent definition runs a linter, a formatter, or a type checker right after a file gets written. Anything broken gets caught seconds after it happens.

Macro is whole-job. When the agent tries to finish, a Stop hook asks the real questions: are the required files there, do exports exist, does the test suite go green. If any of it fails, the agent is not allowed to call the job done.

Team pulls in a second agent. A read-only reviewer opens the builder's work with a clean context window. It is the builder/reviewer pattern, one level higher up the stack.

PostToolUse: Micro Validation on Every Write

Hooks declared inside agent frontmatter only fire when that agent is the one running. Scope is automatic. ESLint belongs to the frontend builder, Ruff belongs to the Python builder, and they never collide.

Here is a Python agent wired to Black and mypy:

# .claude/agents/python-builder.md
---
name: python-builder
description: Build Python modules with automatic formatting and type checking
model: sonnet
hooks:
  PostToolUse:
    - matcher: "Write|Edit"
      hooks:
        - type: command
          command: 'black "$CLAUDE_TOOL_INPUT_FILE_PATH" && mypy "$CLAUDE_TOOL_INPUT_FILE_PATH" --ignore-missing-imports'
---
You are a Python builder agent. Write clean, typed Python code.
Your hooks automatically format with Black and check types with mypy.

The win is scope. These hooks belong to the agent. Nothing leaks into project-level settings. When the orchestrator spins this agent up through the Task tool, the validation goes along for the ride.

Micro catches syntax and formatting. It does not catch a file that never got written. For that, you need a Stop hook.

Stop Hooks: Macro Validation Before Completion

Stop hooks in agent frontmatter turn into SubagentStop events. This script confirms every required output file exists and has the content you asked for:

#!/bin/bash
# .claude/scripts/validate-output.sh
# Validates that agent output meets structural requirements

INPUT=$(cat)
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')

if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
  exit 0
fi

# Check that required files exist
REQUIRED_FILES=("src/components/index.ts" "src/components/Button.tsx")
MISSING=""

for file in "${REQUIRED_FILES[@]}"; do
  if [ ! -f "$file" ]; then
    MISSING="$MISSING $file"
  fi
done

if [ -n "$MISSING" ]; then
  echo "{\"decision\": \"block\", \"reason\": \"Missing required files:$MISSING\"}"
  exit 0
fi

# Check that index.ts contains exports
if ! grep -q "export" src/components/index.ts; then
  echo "{\"decision\": \"block\", \"reason\": \"index.ts has no exports. Add barrel exports for all components.\"}"
  exit 0
fi

exit 0

Then wire it into the agent itself:

---
name: component-builder
description: Build component libraries with output validation
hooks:
  PostToolUse:
    - matcher: "Write|Edit"
      hooks:
        - type: command
          command: 'npx prettier --write "$CLAUDE_TOOL_INPUT_FILE_PATH"'
  Stop:
    - hooks:
        - type: command
          command: "bash .claude/scripts/validate-output.sh"
---

Now both tiers are live. Each write gets formatted. The whole output gets graded before the agent is allowed to stop. If the Stop hook blocks, the agent keeps going until the checks finally pass.

Read-Only Validator Agents

The third tier is a reviewer that cannot touch files. disallowedTools enforces that at the tool layer:

# .claude/agents/output-validator.md
---
name: output-validator
description: Validate agent output without modifying files. Use after builder agents complete.
model: haiku
disallowedTools: Write, Edit, NotebookEdit
---

You are a read-only validator. Your job:

1. Read all files the builder created or modified
2. Verify exports, type safety, and error handling
3. Run the test suite with Bash
4. Report issues as a list. Do NOT fix anything.

If all checks pass, say "Validation passed" with a summary.
If issues exist, list each one with file path and line reference.

Writing files is off the table for this one. Reading and reporting is all it can do. Pair it with a builder via task dependencies:

TaskCreate(subject="Build auth module", description="...")
TaskCreate(subject="Validate auth module", description="Run output-validator on src/auth/")
TaskUpdate(taskId="2", addBlockedBy=["1"])

When to Use Each Tier

Micro only (PostToolUse) is the right fit for small, tight tasks whose quality bar starts and stops with linting. Overhead is low. Feedback is instant.

Micro plus macro (PostToolUse + Stop) suits agents that ship several files with a structural shape attached. The Stop hook catches what a linter never does: files that never got written, logic left half-done, a suite of red tests.

All three tiers belong on code paths you cannot afford to get wrong. The machine-driven checks do the grind. A separate reviewer gives you a second opinion the builder's own hooks could not.

Start the micro tier on the agent you use most. The moment an agent hands you half-finished work, bolt on a Stop hook. Bring in the reviewer once you care that the whole deliverable hangs together, not only that each file parses. Agent configs are fine to keep in your CLAUDE.md, or they can sit as their own files under .claude/agents/, whichever fits the project.

Beyond Single Agents

Think of self-checks and team checks as a stack, not alternatives. Around 90% of the quality bugs get handled by the embedded PostToolUse hooks plus the Stop script before the reviewer ever opens a file. What is left for the reviewer is integration and architecture, not the lint errors the hooks already swallowed.

Continue in Hooks

  • Slash Commands Are Now Skills: Migrating Commands to Skills
    Claude Code slash commands and skills now do the same thing. Here is how to migrate .claude/commands to .claude/skills and why it pays off.
  • Claude Code Setup Hooks
    Braid scripts, agents, and docs into Claude Code setup hooks. One command runs a deterministic script, hands output to a diagnosing agent, logs living docs.
  • Context Backup Hooks for Claude Code
    A StatusLine-driven Claude Code context backup hook. Writes structured snapshots every 10K tokens so auto-compaction never eats errors, signatures, decisions.
  • Cross-Platform Hooks for Claude Code
    Cross-platform Claude Code hooks: skip .cmd, .sh, and .ps1 wrappers and invoke node directly so one .mjs file runs on macOS, Linux, and Windows across the team.
  • Hooks Guide
    Claude Code hooks from first principles: exit codes, JSON output, async commands, HTTP endpoints, PreToolUse and PostToolUse matchers, production patterns.
  • MCP Tool Hooks in Claude Code
    How to call MCP server tools directly from Claude Code hooks using type: mcp_tool — schema, substitution syntax, use cases, and production patterns.

More from Toolkit

  • CLAUDE.md, Skills, Subagents, Hooks: When to Use Which
    Claude Code skills vs subagents vs hooks vs CLAUDE.md: a plain mental model for picking the right primitive, with token costs and examples.
  • Claude Code Subagents: The 3 to 5 Agent Sweet Spot
    Claude code subagents work best at 3-5 concurrent agents. Here is why that ceiling exists, how to set them up, and what to use past it.
  • CLAUDE.md Best Practices: The File That Makes Claude Code Reliable
    CLAUDE.md best practices: keep it under 200 lines, write it by hand, and use hooks when you need real enforcement, not advice.
  • How to Fix Claude Code Running Out of Context
    Claude Code running out of context is a session design problem. Fix it with /compact, lean CLAUDE.md, skills, and subagents, not a bigger window.

Stop configuring. Start building.

SaaS builder templates with AI orchestration.

See what we build for companies →

On this page

PostToolUse: Micro Validation on Every Write
Stop Hooks: Macro Validation Before Completion
Read-Only Validator Agents
When to Use Each Tier
Beyond Single Agents

Stop configuring. Start building.

SaaS builder templates with AI orchestration.

See what we build for companies →