I once asked Claude Code to "rename these 30 files and update every import that references them." A few minutes later it cheerfully reported "Task complete!" — and the next build failed with a wall of red. The renames had landed, but only 14 of the 30 import-rewrite passes had happened. There were no errors in the log. Claude was confident the job was done.
Getting angry at the agent felt unproductive. The more useful question was: why does Claude Code declare completion when it isn't actually finished, and how do I prevent that systematically?
This article shares the prompt structure and verification-loop patterns I converged on after repeating the same mistake three times in production refactors.
Why Claude Code Declares "Done" Too Early
The first thing worth understanding is that this isn't a Claude bug. It's a natural emergent behavior of any agent that has to decide when to stop. Claude Code's internal "I've done enough" judgment is shaped by three signals:
- Cumulative token spend: After many consecutive edits, the agent leans toward closing out the turn rather than starting another long batch
- Absence of error feedback: If you didn't run a build or tests, Claude infers "things probably work" and treats the absence of contradicting evidence as confirmation
- Ambiguous completion criteria: Vague instructions like "fix all of them" let Claude pick its own boundaries, often defaulting to whatever felt like a clean stopping point
All three are addressable from your side — through prompt design and verification flow. The model isn't lazy. We're just failing to hand it a precise definition of "done."
Three Ingredients of a "Don't Stop" Prompt
Whenever I now ask Claude Code to do anything that touches more than a handful of files, my prompt always carries these three pieces.
1. Numerical completion criteria
Kill the ambiguity by giving exact counts.
❌ Bad:
"Replace all console.log calls under src/ with logger.info"
✅ Good:
"Replace all console.log calls under src/ with logger.info.
Before starting, run grep -r 'console.log' src/ and note the count.
After the work, grep again and verify the count is 0 before reporting completion."Just adding the "pre-count → work → post-count must be 0" protocol makes Claude Code dramatically more likely to actually finish. The pre-count step is what really matters: it forces the agent to commit to a target number that you can later verify.
2. Explicit checkpoints
For longer tasks, demand intermediate progress reports. This is the single most effective hedge against premature completion.
✅ Good:
"Rename 30 files. After every 10 files, report progress in the form
'[X/30] complete' on a single line, then continue with the next batch."The explicit checkpoint forces Claude to maintain an internal counter of remaining work. When the model writes [20/30] complete, it's much harder for the next message to be "task complete" without addressing the missing 10 files.
3. A required completion-report format
Finally, lock down the shape of the completion message itself.
✅ Good:
"When complete, report using exactly this template:
- Files changed: N
- Build: PASS / FAIL
- Tests: PASS (N tests) / FAIL (N tests)
- Remaining work: none / yes (details)
Do NOT write 'complete' if Build or Tests is FAIL."Explicitly telling the model "don't say complete if the build is red" sounds trivial, but it works remarkably well. The fixed template forces Claude to gather concrete evidence before it can fill in the blanks.
The Verification Loop Pattern
Prompt tweaks have a ceiling. The pattern I now ship in every production project is having Claude Code run an external verification script on itself. Here's a minimal Bash implementation:
#!/usr/bin/env bash
# verify-rename.sh — guarantees a rename pass completed end to end
set -euo pipefail
OLD_PATTERN="$1" # e.g. "console.log"
NEW_PATTERN="$2" # e.g. "logger.info"
TARGET_DIR="${3:-src}"
# 1. Count remaining occurrences of the old pattern
remaining=$(grep -r --include="*.ts" --include="*.tsx" \
-l "$OLD_PATTERN" "$TARGET_DIR" 2>/dev/null | wc -l)
# 2. Verify the build still passes
build_status="PASS"
if ! npm run build > /tmp/build.log 2>&1; then
build_status="FAIL"
fi
# 3. Verify the test suite still passes
test_status="PASS"
if ! npm test -- --run > /tmp/test.log 2>&1; then
test_status="FAIL"
fi
echo "----- VERIFICATION REPORT -----"
echo "Remaining files with '$OLD_PATTERN': $remaining"
echo "Build: $build_status"
echo "Tests: $test_status"
# 4. Exit non-zero if any condition isn't met
if [[ "$remaining" -ne 0 || "$build_status" != "PASS" || "$test_status" != "PASS" ]]; then
echo "❌ NOT COMPLETE — Claude must continue working"
exit 1
fi
echo "✅ ALL CHECKS PASSED"Drop this in scripts/verify-rename.sh, then end your prompt with: "Run bash scripts/verify-rename.sh console.log logger.info src and keep working until the exit code is 0." The "build is red but Claude said done" failure mode disappears almost entirely.
The crucial design choice here is exit-code semantics. By making "incomplete" a non-zero exit, you give Claude an unambiguous signal in its tool output. Anything that looks like an error will pull the agent back into work mode without you having to negotiate.
Pair It With Hooks for Full Automation
If you wire the verification script into Claude Code's Hooks system, you get fully automated enforcement. A PostToolUse hook on the Edit tool can run your verifier after every file change, so the verification result lands back in Claude's context before it ever has a chance to declare completion.
Example .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "bash scripts/verify-rename.sh console.log logger.info src 2>&1 | tail -5"
}
]
}
]
}
}When Claude sees Remaining: 14 flow into its context, it immediately recognizes "not done" and resumes work. The tail -5 keeps the hook output short so it doesn't bloat the context window — only the summary lines are pushed in, not the full build log. For a deeper dive into hook design, see the Claude Code Hooks practical guide.
One word of caution: don't run the full test suite in a hook if it takes more than a few seconds. Hook output is synchronous and slow tests will frustrate the workflow. For long suites, run only the affected tests in the hook and the full suite only at the final verification step.
What If Claude Resists the Format?
Occasionally Claude Code will skip parts of the completion template — usually when something genuinely failed and the model is trying to soft-pedal the result. When that happens, I respond with a single line: "Re-run verification and report using the exact template — the response is missing the Build line." The model recovers immediately.
If you find yourself doing this often, your prompt is probably under-specifying the template. Add a one-line example of a well-formed report at the end of your instructions and the issue usually goes away.
My Reusable Prompt Template
Here's the template I now reach for whenever a task crosses many files. Copy and adapt:
## Task
[1–2 sentence description of the change]
## Completion criteria (do not declare done unless ALL are met)
1. Target file count: pre-count says N, post-grep returns 0
2. Build passes: `npm run build` succeeds
3. Tests pass: `npm test` succeeds
4. Verification script: `bash scripts/verify-task.sh` exits 0
## Intermediate reports
Every 10 files, report `[X/N] complete` on a single line, then continue.
## Completion-report format
- Files changed: N
- Build: PASS / FAIL
- Tests: PASS (X tests) / FAIL
- Remaining work: none / yes (details)
Do NOT report "complete" if Build or Tests is FAIL.Since switching to this template, my "Claude said done but only finished half" incidents have dropped to zero. The boilerplate feels heavy at first, but it's far cheaper than the rework cost of skipping verification.
Start Today: Write One Verification Script
The fastest way to a Claude Code that actually finishes is to pick one repetitive task in your current project — renames, refactors, import cleanup — and write a 10-line shell script that returns a numerical completion signal. Once it exists, every future task in that family runs to true completion automatically.