AniUI Academy

Giving Claude Something to Verify

Claude stops when the work looks done. Four escalating ways to give it a real check instead — in the prompt, as a session goal, as a Stop hook, and as a second opinion.

10 min read

This is the most important habit in the course. If you take one thing from these thirty-two lessons, take this one.

Why Claude stops early

Claude stops when the work looks done.

That sentence is worth sitting with, because it explains a failure you will otherwise find mysterious. Claude is not being lazy or overconfident. It has finished editing, nothing in front of it says anything is wrong, and there is no further signal to act on. "Looks done" is the only evidence in the room.

Which makes you the verification loop. Every mistake sits there until you happen to notice it. You read the diff, you run the app, you spot the thing, you come back and describe it. That is a slow loop with a human in the middle of it, and it is why some people find agents exhausting rather than useful.

The check can be almost anything that returns a signal Claude can read: a test suite, a build's exit code, a linter, a script that diffs output against a fixture, or a screenshot compared against a design.

Level one: ask for it in the prompt

The cheapest version, available on any task right now, is one extra sentence.

implement a function that validates email addresses
write a validateEmail function. example test cases:
user@example.com is true, invalid is false, user@.com is false.
run the tests after implementing

The same move works visually — paste a screenshot, ask for the implementation, then ask for a screenshot of the result compared against the original. And it works on failures: rather than "the build is failing", give the error, ask for the build to be verified afterwards, and say explicitly to address the root cause rather than suppress the error.

claude
  1. You typed: Add a slug field to Post. It must be unique, lowercase, and derived from the title. Write tests for those three properties first, then implement until they pass.

  2. Claude ran: Read prisma/schema.prisma

  3. Claude ran: Write tests/post-slug.test.ts

  4. Claude ran: Run: npm test post-slug

  5. Output: FAIL  post-slug.test.ts  (0 passed, 3 failed)
  6. WhyThree failing tests is the point of this step, not a problem with it. The session now contains a definition of done that neither of you can talk your way out of.

  7. Claude ran: Edit prisma/schema.prisma

  8. Claude ran: Edit src/models/post.ts

  9. Claude ran: Run: npm test post-slug

  10. Output: FAIL  post-slug.test.ts
      uniqueness: expected reject, received created
      (2 passed, 1 failed)
  11. WhyHere is the moment that would otherwise have been yours. Without the tests, this session ends now with a confident summary and a slug field that silently allows duplicates.

  12. Claude ran: Edit prisma/schema.prisma

  13. Claude ran: Run: npm test post-slug

  14. Output: PASS  post-slug.test.ts  (3 passed)
  15. Claude replied: All three pass. The uniqueness constraint was missing from the schema — I had only enforced it in application code, which does not hold under concurrent writes.

One extra sentence in the prompt, and the bug is caught by the session rather than by you in a fortnight.

Level two: a condition for the whole session

The prompt version has a weakness: it applies to one message. Ten turns later, after a compaction, the instruction may be gone.

/goal sets a completion condition that outlives the turn.

/goal all tests in test/auth pass and the lint step is clean

Setting a goal starts a turn immediately. After each turn finishes, a separate small fast model reads the conversation and judges whether the condition holds. If it does not, Claude begins another turn rather than handing control back to you, and it receives the evaluator's reason as guidance for what to do next. When the condition is finally met, the goal clears itself.

Run /goal with no argument to see the current status, including how long it has been running and the evaluator's most recent reason. Run /goal clear to abandon it. One goal is active at a time.

The important constraint is about what you can ask for. The evaluator reads the conversation. It does not run commands or read files. So the condition has to be something Claude's own output can demonstrate. "All tests in test/auth pass" works, because Claude runs the tests and the result lands in the transcript where the evaluator can see it. A condition about a property nobody ever printed cannot be judged.

Good conditions have one measurable end state, a stated way to prove it, and any constraint that must hold on the way there. You can bound the run by adding something like "or stop after 20 turns".

One thing /goal does not do is change permissions. Claude will still stop and ask before actions your settings do not already allow, which rather defeats the point if you have walked away — which is why goals pair naturally with auto mode.

Level three: a gate that cannot be forgotten

Both levels so far live inside the conversation, and anything inside the conversation is advisory. Claude might not run the check. It might run a different one.

A Stop hook is a script in your settings that Claude Code runs whenever the main agent finishes responding. Your script decides whether the turn is allowed to end. If it blocks, it supplies a reason, Claude reads that reason, and the conversation continues instead of stopping. This is deterministic in a way a sentence in a prompt is not: it runs whether or not Claude remembered.

There is a safety valve: if a Stop hook blocks eight times in a row, Claude Code overrides it and lets the turn end, so a hook that can never be satisfied cannot trap you in a loop.

You do not need to write one yet — hooks have their own lesson later, and Claude can write the hook for you when you get there. /goal, as it happens, is a built-in shortcut for exactly this mechanism, scoped to one session.

Level four: a second opinion

The three levels above all have the same blind spot: the agent doing the work is the one deciding it is finished. A check it wrote itself, against criteria it chose, is not an independent test.

A verification subagent fixes that. A subagent runs in a fresh context and sees only the diff and the criteria you give it, not the reasoning that produced the change, so it evaluates the result on its own terms. Claude Code also ships a /code-review skill that reviews your current diff for bugs in a fresh subagent and returns what it finds to your session.

For checking work against a plan rather than for bugs generally, write the prompt yourself and be specific about what counts:

use a subagent to review the rate limiter diff against PLAN.md.
check that every requirement is implemented, the listed edge cases
have tests, and nothing outside the task's scope changed. report
gaps, not style preferences.

Be ready for the failure mode. A reviewer asked to find gaps will find some, because that is the job it was given, and chasing all of them leads straight to over-engineering: extra abstraction, defensive branches, tests for things that cannot happen. Tell it to flag only what affects correctness or the stated requirements, and treat the rest as a suggestion.

In the prompt

Ask for the check and the iteration in the same message. No setup, works on any task today, lasts one message.

As a session goal

/goal sets a condition an evaluator re-checks after every turn. Survives the whole session. Judged from what is in the conversation.

As a Stop hook

A script that refuses to let the turn end until your check passes. Deterministic, applies to every session in its scope, needs writing once.

As a second opinion

A subagent in a fresh context grades the diff instead of the author grading itself. Catches the class of mistake the other three share.

Each step trades setup effort for attention. The prompt version is what you use today. The goal and hook versions are what let a run finish correctly while you are not watching.

Ask for evidence, not assertion

One habit that costs nothing. Ask Claude to show the evidence rather than assert success: the test output, the command it ran and what came back, the screenshot.

"All tests pass" is a claim. Twelve lines of test output is a fact. Reading the evidence is faster than re-running the verification yourself, and it is the only way to review a session you were not watching.

When there is no check to give

Sometimes nothing verifiable exists yet — no tests, no build that fails usefully, nothing to diff against. That is worth noticing rather than working around, because it means you cannot tell whether this change or any future one is correct. Often the right first task is to have Claude build the check: a test for the current behaviour, a script that exercises the path, a fixture to compare against. Slower once, faster every time after.

What to take away

Claude stops when the work looks done, so without something that returns a pass or a fail, you become the verification loop and every mistake waits for you to notice it. Four levels of check are available, and they escalate: ask for it in the prompt, set it as a session-long condition with /goal, enforce it as a Stop hook that will not let the turn end, or hand the diff to a subagent in a fresh context so the author is not the grader. Ask for evidence rather than a claim of success. And where no check exists, building one is usually the real first task.

Next: doing the reading and the thinking before any code is written, and knowing when that step is a waste of time.

Check yourself

5 questions · pass 4/5 to unlock Explore, Plan, Then Code

up to 50
  1. 1.Why does Claude stop before the work is actually finished?

  2. 2.What does the /goal command actually do?

  3. 3.Why must a /goal condition be something Claude's own output can demonstrate?

  4. 4.What is the practical difference between a Stop hook and asking for the check in your prompt?

  5. 5.You ask a subagent to review a diff and it returns eleven findings. What should you assume?

5 left to answer