Build This Now
Build This Now
Keyboard ShortcutsStatus Line Guide
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.

Published 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

  • 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.
  • Claude Code Permission Hook
    Install a three-tier Claude Code permission hook: instant allow for safe calls, instant deny for dangerous ones, LLM check for the gray area. No skip flag.

More from Toolkit

  • Keyboard Shortcuts
    Configure Claude Code keybindings.json: 17 contexts, keystroke syntax, chord sequences, modifier combinations, and how to unbind any default shortcut instantly.
  • Status Line Guide
    Set up a Claude Code status line for model name, git branch, session cost, and context usage. settings.json config, JSON input, bash, Python, Node.js scripts.
  • AI SEO and GEO Optimization
    A rundown of Generative Engine Optimization: how to get content cited inside ChatGPT, Claude, and Perplexity responses instead of just ranked on Google.
  • Claude Code vs Bolt.new: Which Should You Use?
    Bolt.new prototypes in 28 minutes with zero setup. Claude Code takes 90 minutes but ships production-ready code. Here is how to pick the right tool.

Stop configuring. Start building.

SaaS builder templates with AI orchestration.

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.