Claude stops when the work looks done

Anthropic’s own documentation states the problem this article is about as plainly as it can be stated. Without something Claude can run and read the result of, “‘looks done’ is the only signal available, and you become the verification loop: every mistake waits for you to notice it” [5]. That sentence names the actual failure mode. An agent that reads files, edits them, and runs commands can produce a diff that reads as finished — sensible structure, plausible naming, a plan that was followed to the letter — while still being wrong in a way that only surfaces when something forces the claim to be tested against reality rather than read for plausibility.

“Verification” in a Claude Code workflow is not one thing. It is four separable mechanisms, each closing a different gap between looking done and being done, and each with a distinct failure mode when it is missing: a runnable check that turns “looks done” into a pass-or-fail signal; a hook that enforces a rule regardless of what Claude decides to do; a sandbox that bounds the damage an unverified command can cause while it is being tried; and a permission or approval gate that keeps specific categories of decision with a human, structurally, rather than by convention. A sibling piece in this series covers how hooks are built and configured across Claude Code’s lifecycle events in detail; this one is about what each of the four layers actually verifies, why none of them substitutes for the others, and where their boundaries sit in a working setup.

The throughline is a distinction that is easy to state and easy to lose in practice: a model’s own assessment of its work is not evidence, however articulate it sounds. Evidence is something a person or a deterministic process outside the model can independently check. Everything below is an elaboration of that one idea, applied at four different points in a session.

ADVERTISEMENT

The loop only closes if the check is a real oracle

A “check” sounds simple to add, but software-testing research has spent decades on how easy it is to get wrong, and the problem has a name. Given an input, “the challenge of distinguishing the corresponding desired, correct behaviour from potentially incorrect behaviour is called the test oracle problem” [11]. A test closes the verification loop only if whatever decides pass or fail — the oracle — actually tracks correctness. A test that always passes, a build step that checks only that a command exited without asserting what it produced, and a human skimming a diff and pattern-matching against “code that looks familiar” are all checks in name, and none of them is a working oracle.

Claude Code’s documentation is built around exactly this distinction between having a check and having a check that means something. Its stated advice is to hand Claude “something that produces a pass or fail” — a test suite, a build exit code, a linter, a script that diffs output against a fixture, or a screenshot compared against a design — so that “Claude does the work, runs the check, reads the result, and iterates until the check passes” [5]. The mechanism that makes this durable rather than a one-off request is the Stop hook: a script that runs when Claude would otherwise end its turn, able to block the turn from ending until the check it runs succeeds. Because a script could loop on a broken check forever, Claude Code caps it — the hook “overrides… and ends the turn after 8 consecutive blocks” [1] — turning an open-ended retry loop into a bounded one, at the cost of accepting that eight failed attempts eventually have to surface to a person rather than looping past them.

That retry loop is also where an older problem in software testing shows up: the flaky check. Flaky tests, ones that can pass or fail on an unchanged codebase, were first studied systematically across 201 commits that fixed flaky-test issues in 51 open-source projects, and the paper’s framing of the assumption a flaky test violates is worth restating directly: “an unmodified test is expected to either always pass or always fail for the same code under test” [10] — exactly the assumption a Stop-hook retry loop depends on. If the check itself is flaky, rerunning it until it passes does not verify the change; it samples a noisy process until it lands on green, and the retry budget meant to give a broken check time to surface instead gives a good change more chances to get unlucky and a broken one more chances to slip through by chance. A check earns the right to gate a loop only once its own signal has been shown to track what it claims to measure on unchanged code, before it is trusted to judge changed code.

A rack of matte-black test runner blades with one blade drawn slightly proud of its neighbours, its status light caught glowing at the exact midpoint between amber and green
Figure 1. A check only closes the loop if its pass or fail signal is a real oracle; a light mid-transition is neither a pass nor a verdict yet.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

A hook is the one thing that isn’t asking

Everything above assumes Claude chooses to run the check, and that assumption does a lot of work. It is the reason Claude Code draws a hard line between instructions and enforcement. CLAUDE.md files, system prompts, and in-conversation reminders all shape what Claude tries to do; none of them constrain what Claude Code actually allows, because they are read and interpreted by the same model whose judgment is the thing in question. Anthropic’s own phrasing about which side of that line a hook sits on is unambiguous: “unlike CLAUDE.md instructions which are advisory, hooks are deterministic and guarantee the action happens” [5].

The mechanics back this up. A PreToolUse hook runs before a tool call executes and can veto it outright; a hook that exits with status code 2 blocks the action regardless of what the model wanted, to the point that “even a JSON permissionDecision of allow can’t override it” [1]. A hook that inspects a proposed Bash command for a destructive pattern and denies it isn’t asking Claude to reconsider — it refuses the call before it reaches the shell, the same way a PostToolUse hook wired to the test suite hands Claude a result it did not generate and cannot argue with. A sibling article in this series covers this event-by-event; the point for verification specifically is narrower: a hook is where a rule that must hold with zero exceptions gets to actually hold with zero exceptions, instead of holding on average, when the model remembers, when the prompt is well formed.

ADVERTISEMENT

Why that distinction matters more for AI-generated code than hand-written code has direct empirical support, and it cuts against a comfortable assumption. In a controlled study, programmers with access to an AI coding assistant “wrote significantly less secure code than those without access” on security-relevant tasks, and in the same study were “more likely to believe they wrote secure code than those without access” [9]. Read together, those findings describe exactly the failure a hook is built to close: not that the assistance produced worse output on average, but that the people using it — and, by direct extension, a model narrating its own confidence — were systematically worse at noticing when it had. A deterministic check does not share that miscalibration, because it is not assessing anything. It is either satisfied or it isn’t.

A close view of an electromechanical latch bolt caught mid-throw between its housing and a striker plate on a cage door frame, the door standing part-open behind it
Figure 2. A hook is deterministic where an instruction is only advisory; the bolt either seats or it does not, regardless of what anyone intended.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

Sandboxing is what makes it safe to let the loop run unattended

A verification loop that requires approval for every command it runs is not actually unattended; it has just moved the bottleneck from “does the work look done” to “should I click yes again.” Claude Code’s own account of why it built a sandbox names that trade-off directly: constant permission prompts create “approval fatigue” that “paradoxically reduces security as users stop carefully reviewing requests” [6]. The sandbox exists to let a much larger set of commands run without a prompt at all, by changing what happens if one of them is wrong rather than by asking a human to pre-judge each one.

The mechanism is boundary enforcement at the operating-system level, not at the model. The sandboxed Bash tool lets Claude run most shell commands without stopping to ask permission because “you define which files and network domains commands can touch, and the operating system enforces that boundary for every Bash command and its child processes” [3], built on Linux bubblewrap and macOS’s Seatbelt framework rather than a container or virtual machine Claude Code has to manage itself. Filesystem and network isolation are both required, for a specific reason: a filesystem boundary without a network boundary still lets a mistaken or compromised process exfiltrate what it can read, and a network boundary without a filesystem boundary still lets it reach credentials it should never touch. Git credentials in particular never enter the sandbox at all; a proxy validates git operations and applies tokens from outside the boundary, so the loop can commit and push without ever holding the keys that would let it do so somewhere else [6]. Reported internally, this design “reduces permission prompts by 84%” while keeping the boundary enforced by the kernel rather than by the model choosing to stay inside it [6] — a vendor-reported figure from Anthropic’s own usage, not an independently audited one, but a concrete claim about the trade this specific mechanism makes.

Namespace-and-seccomp isolation of this kind sits at one end of a much older design space, not a new one. Amazon’s Firecracker microVM was built for exactly the situation where cloud operators had judged “a choice between virtualization with strong security and high overhead, and container technologies with weaker security and minimal overhead” unacceptable, and resolves the trade by giving each workload its own lightweight virtual machine rather than a shared kernel — a design now running “millions of production workloads and trillions of requests per month” underneath AWS Lambda and Fargate [13]. Claude Code’s sandbox sits closer to the container end of that spectrum: cheaper to start, enforced by the same kernel the host already runs, and for that reason a boundary against mistakes and prompt-injected commands rather than a boundary suitable for running arbitrary code from a fully untrusted party on shared hardware. Which point on that curve is appropriate is a question about what is being isolated from what, not a question with one right answer — an agent editing a known repository on a developer’s own machine and a multi-tenant platform running arbitrary customer code are different threat models, even though both are correctly described as sandboxed.

A patch panel at the edge of a mesh-walled test pod with one uplink cord caught part-way out of its port and an empty capped port waiting beside it
Figure 3. A sandbox does not prove a command was correct; it bounds what a wrong one can reach while it is being tried.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

Where judgment has to stay outside the loop

Sandboxing and hooks both answer “how do we let Claude act freely without trusting every individual action.” Permissions answer a different question: which categories of action should not be freely available at all, regardless of how well-isolated the environment is. Claude Code’s permission system is explicit that this boundary is not a matter of persuading the model: “permission rules are enforced by Claude Code, not by the model. Instructions in your prompt or CLAUDE.md shape what Claude tries to do, but they don’t change what Claude Code allows” [2]. Rules are evaluated in a fixed order — deny, then ask, then allow, with the first match winning regardless of how specific a competing rule is [2] — so it is the human who wrote the ruleset, not the model interpreting a request in the moment, who decides what counts as consequential enough to stop for.

The permission modes make the range concrete. The default mode prompts on first use of each tool; acceptEdits waives that for file edits and routine filesystem commands; plan mode lets Claude read and explore without being able to change anything at all; and bypassPermissions skips prompts almost entirely and is meant only for environments — containers, disposable virtual machines — “where Claude Code can’t cause damage” if the bypass turns out to be wrong [2]. Even that last mode keeps a small set of prompts that fire regardless of the mode selected, including a check against removing the filesystem root or home directory “including when the command contains command substitution… or process substitution,” which the documentation frames directly as “a circuit breaker against model error” [2] — a gate that exists specifically because a model can be confidently, fluently wrong about a command’s blast radius, and the fix is not to make the model more careful but to make certain actions require a human regardless of how careful the model believes itself to be.

ADVERTISEMENT

That framing — a structural check on confidence rather than a request for more of it — has support outside Claude Code’s own documentation. A randomized trial of sixteen experienced open-source developers working 246 real issues on their own repositories found that developers allowed to use AI tools took 19% longer to complete issues than those who weren’t, while believing afterward that the tools had sped them up by roughly 20% [8]. The point is not that the tools were net negative in that specific setting; the study’s own authors are careful that this is a snapshot of one moment’s tooling on one kind of task, not a general verdict. The point is that the developers’ real-time sense of whether the collaboration was working was measurably wrong, in the optimistic direction, on the same axis “looks done” fails on for a model. If experienced humans miscalibrate their own trust in an AI-assisted loop, then building a gate around a category of action rather than around a feeling — this class of decision always stops here, independent of how confident anyone in the loop currently feels — is doing real work, not redundant caution.

A brushed-steel two-key interlock panel with a brass key turned fully clockwise in the left keyway and the right keyway standing empty, waiting
Figure 4. A permission rule is enforced by the console, not by whoever is asking; the empty keyway holds regardless of how confident anyone feels.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

Independent checks compose; correlated ones don’t

Stacking verification layers only helps if the layers actually fail differently. Two checks with the same blind spot give the illusion of double coverage while catching nothing an additional single check wouldn’t have caught alone. It is worth making that precise, because it is the real argument for using hooks, sandboxes, and human gates together rather than leaning harder on any one of them.

Say check AA independently has recall rAr_A against a real defect — the fraction of the time it would catch that class of problem if it ran — and check BB has recall rBr_B. If the two checks fail for unrelated reasons, the chance a defect slips past both is the product of the chances it slips past each:

Pmiss=(1rA)(1rB) P_{miss} = (1-r_A)(1-r_B)

which is smaller than either (1rA)(1-r_A) or (1rB)(1-r_B) alone: real, additive protection. But if the two checks share a failure mode — most obviously, if the “second check” is the same model re-reading its own output inside the same context, primed by the same reasoning that produced the mistake — their misses stop being independent, and the joint miss probability climbs back toward max(1rA, 1rB)\max(1-r_A,\ 1-r_B): no better than the stronger of the two checks alone, and often worse in practice, because the second pass inherits the first pass’s blind spot along with its confidence.

This is the actual justification for the pattern Claude Code documents for independent review: running a check “by a second opinion,” through “a verification subagent… [that] has a fresh model try to refute the result, so the agent doing the work isn’t the one grading it” [5]. Subagents run in their own context window, with their own permissions and tool access, deliberately separate from the session that produced the change [4]; a reviewer built this way “sees only the diff and the criteria you give it, not the reasoning that produced the change” [5], which is precisely the condition under which its failure mode has a chance of differing from the writer’s. The same logic supports pairing a human review with it rather than treating the two as redundant. An empirical study of code review at Microsoft, built on observing, interviewing, and classifying review comments across teams, found that “finding defects remains the main motivation for review” but that reviews turn out to be “less about defects than expected” in practice, instead delivering knowledge transfer, awareness of ongoing work across a team, and alternative solutions surfaced by a second person thinking about the same change from outside it [12]. That is a different kind of signal than a fresh-context subagent produces, not a weaker version of the same one, which is the whole case for keeping both rather than treating either as sufficient alone.

A thermal ticket printer at a console with a pale verification strip caught curling half out of its slot, and a second console standing apart from the main one, angled toward it
Figure 5. Independent checks add real coverage; a second pass from the same reasoning mostly inherits the first pass's blind spot along with its confidence.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

What “verified” actually has to mean

Put the four mechanisms next to each other and the shape of a working setup becomes clear. A check with a real oracle closes the loop Claude runs in. A hook makes specific rules hold with no exceptions instead of most of them. A sandbox bounds what an unverified action can reach while it is being tried. A permission or approval gate keeps a fixed set of consequential decisions with a human regardless of how confident anyone in the loop currently is. None of the four substitutes for another: a hook that blocks a dangerous command doesn’t tell you whether the safe command that ran instead was correct; a sandbox that contains a mistake doesn’t prevent the mistake from being committed; a human gate on a git push doesn’t verify anything about what is inside the commit being pushed.

Anthropic’s own account of how Claude Code gets used in practice makes the composite nature of “verified” explicit rather than leaving it implied. Their research distinguishes “judged success” — a transcript read as accomplishing the stated goal — from a stricter “verified success,” which additionally requires “verifiable evidence of success,” specifically “git activity like commits and pull requests matching the work, as well as test suites passing, and explicit affirmation from the user” [7]. In the same data, that stricter bar is where expertise shows up most clearly: novice-led sessions reached verified success in roughly 15% of cases against 77% partial success, while sessions led by more experienced users reached 28 to 33% verified success at similarly high partial-success rates [7]. The gap between looking like it worked and checkably having worked does not close on its own as a model gets more capable. It closes when whoever is running the session insists on the harder bar and has the four mechanisms in place to check it.

None of this is a case for distrusting Claude Code specifically, and it would misread the evidence to conclude that a human reviewing every line by hand is the safer default: the same research found code-producing sessions land non-software professionals within seven points of software engineers on verified success, and the flaky-test and code-review findings cited above apply just as much to unaided human work as to AI-assisted work — software engineering has needed better oracles, harder gates, and less-correlated review for as long as it has had bugs. What changes with an agent that can read, write, and run commands on its own is the speed at which “looks done” gets generated, and therefore how quickly a workflow without a real check, a real hook, a real sandbox, and a real gate runs out of runway before something slips through one of them. The four mechanisms in this piece are not a checklist particular to one product. They are what “verified” has always required, arranged around the specific points in a Claude Code session where a model’s own judgment is, by design, not the thing being trusted.