Claude Code

Give Claude a check to run: tests, goals, Stop hooks and spec reviews

TL;DR

Claude stops when work looks done. Give it something that returns pass/fail (tests, typecheck, a Stop hook, a subagent review) so it closes the loop, and ask for real output as evidence. Rules that must always hold belong in hooks.

Claude stops when the work looks done. If there's nothing to run that returns pass/fail, "looks done" is all it has to go on, and in practice you become the test suite: every bug sits there until you happen to find it. This is one of the most important ideas on Claude Code's best practices page. This post explains it along with a companion concept: telling apart instructions that Claude decides on from instructions that Claude Code enforces.

Two kinds of instructions

AspectClaude decides (non-deterministic)Claude Code enforces (deterministic)
What to doPrompts, CLAUDE.md, skillsHooks, skills only you can invoke
What it may touchAuto mode's classifierDeny rules, sandbox, managed settings
When it's doneAsking for evidence, /goal, /code-reviewTests, typecheck, Stop hook, CI
How much it spendsEffort level--max-turns, --max-budget-usd, spend limit

Most of the time, let Claude decide: with real code in front of it, the model usually judges better than a pre-written rule. The deterministic kind is for things that must always happen (or must never happen). A tell: if you keep adding "IMPORTANT" to a CLAUDE.md line and Claude still ignores it, that line should become a hook.

Give Claude a check it can run

A check can be anything that returns a result Claude can read: a test file, a build's exit code, a linter, a script comparing output against a fixture, or a browser screenshot next to the mockup. If the repo already has one, the most useful line in CLAUDE.md is the command that runs it.

Four levels of strictness, from light to hard:

  1. In the prompt: run the auth tests after editing, keep going until they pass. No setup needed.
  2. Set a goal: /goal all tests in tests/auth pass and lint is clean. A separate model checks the condition after every turn, useful for long background sessions.
  3. Stop hook: a script that runs whenever Claude is about to end its turn; exit code 2 sends Claude back to work.
  4. Someone else reviews: /code-review runs in a fresh subagent, uninfluenced by the reasoning that produced the code.

Stop hook: enforce typecheck before finishing

// .claude/settings.json
{
  "hooks": {
    "Stop": [
      { "hooks": [
        { "type": "command", "command": "pnpm tsc --noEmit 1>&2 || exit 2" }
      ] }
    ]
  }
}

Error output goes to stderr so it returns to Claude as the reason it "can't stop yet". There's a safety cap: if the hook blocks 8 times in a row with no tool call in between, Claude Code lets the turn end (adjust with the CLAUDE_CODE_STOP_HOOK_BLOCK_CAP environment variable).

Ask for evidence, not promises

Whichever method you use, ask Claude for evidence: the command it ran and its output, or a screenshot. "All tests pass" tells you almost nothing; reading the output is faster than rerunning it yourself. For sessions running while you're not watching, it's the only evidence you have.

Review against the spec, skip style

use a subagent to review the rate limiter diff against SPEC.md,
check every requirement is implemented and the listed edge cases
have tests, report gaps that affect correctness and skip style

The docs stress not dropping "skip style": a reviewer asked to find problems will always find something. Fix every finding and you'll end up with extra abstraction layers and tests for cases that can't happen.

Risks and caveats

  • Wrong tests, wrong loop: Claude will "make it pass" against the tests you give it. Review tests carefully before handing over an automated loop.
  • Hardcoding to pass: if you see code special-casing test data, say explicitly "the solution must be general".
  • Slow hooks: typechecking the whole repo on every stop can take time; consider checking only the changed parts.

How to apply it

  • Dev: Add one sentence to every bug-fix prompt: "write a test that reproduces it first, fix until it passes, show me the output".
  • Tech Lead: Put the test/typecheck/lint commands in CLAUDE.md. For rules with no exceptions (don't edit src/gen/, don't read .env), move them into hooks or deny rules.
  • PM: Write acceptance criteria that can be verified. That's exactly the "check" an agent needs to close the loop on its own.

Sources & references

  1. Best practices for Claude Code — Claude Code Docs