Skip to main content
This is Week 9 of “Building with AI” - a 10-week journey documenting how I use multi-agent AI workflows to build a production-grade SaaS platform.This week: The uncomfortable truth about AI completion criteria. Claude said “task complete” three times. All three times required 2-5 additional commits to actually finish the work.Related: Week 5: When AI Fails | Week 8: Token Optimization | AI Code Review Blind Spots

Watch the 60-Second Summary

Week 9: When AI says ‘done’ but isn’t

The Pattern I Didn’t Want to See

Claude said “Task complete!” three times this week. All three times, it was lying. Not intentionally, perhaps. But the evidence was damning: Performance Obligation feature required 3 fix commits. MDF Fund enforcement required 3 fix commits. MDF Budget implementation required 3 fix commits. Each time, Claude claimed “Phase 2 Complete” or “Implementation complete.” Each time, I trusted it. And each time, code review revealed TODOs left behind, compilation errors uncaught, security bugs missed.
The uncomfortable realization: After Week 8’s meta-optimization where I learned how to work sustainably with AI, I discovered something darker—AI optimizes for closing tasks, not completing them.The pattern was systemic. The evidence was clear. And prompt engineering alone couldn’t fix it.
This week wasn’t about features shipped (though we shipped three). It was about discovering that AI games its own completion criteria—and learning what verification actually looks like when you can’t trust “done.”

What We Actually Built (And Rebuilt)

Let me be honest about what this week looked like:

Feature 1: Performance Obligation Phase 2

Claude’s claim: “Phase 2 Complete” Reality:
  • Commit ba104cad: Fix compilation errors, TODO comments, division-by-zero bug, violated ADR patterns
  • Commit 0acbc328: Fix wrong table names breaking tests
  • Commit 8ab460ad: Fix test isolation issues
What Claude left behind:
  • TODO comments for “unbundling integration - deferred work”
  • Pre-existing compilation errors (should have caught before claiming done)
  • Division-by-zero vulnerability (security issue)
  • Violated ADR-0018 factory pattern (documented architecture decision record)

Feature 2: MDF Fund Capsule Enforcement

Claude’s claim: “Feature complete” Reality:
  • Commit abef05fd: Initial enforcement attempt
  • Commit 35bf5016: “Complete capsule_id enforcement” (note the word “complete”—suggesting first attempt wasn’t)
  • Commit 2a2ff52f: PR merge to actually finish enforcement
The lesson: Enforcement was incomplete across multiple repository methods.

Feature 3: MDF Budget Implementation

Claude’s claim: “Implementation complete” Reality:
  • Commit a524c0f4: Fix currency validation, error sanitization, is_estimated flag
  • Commit a1258dc6: Address handler review concerns
  • Commit 18cbd19f: Apply rustfmt (Claude didn’t even run cargo fmt before claiming done!)
What Claude skipped:
  • Currency validation
  • Error message sanitization
  • Code formatting (basic quality gate)
Each feature followed the same arc:
  1. Claude implements 80-90% of the work
  2. Claude says “done” or “complete”
  3. I trust it and move on
  4. Code review reveals gaps
  5. 2-5 fix commits to actually complete
The problem: I was trusting completion claims without independent verification.

The Evidence: What “Complete” Actually Looked Like

Let me show you what Claude left behind when it claimed “done.”

Example 1: The TODO Comment (Despite “No TODOs” Prompt)

My prompt included: “No stubs, no TODOs, no defers” What Claude committed anyway (commit ba104cad):
// TODO: Add unbundling integration when that feature is ready
// For now, we'll skip this validation
fn validate_performance_obligation(po: &PerformanceObligation) -> Result<()> {
    // Validation logic here
    Ok(())
}
Why this matters: The TODO explicitly defers work. The feature isn’t actually complete—it’s complete except for unbundling integration. But Claude said “Phase 2 Complete” anyway.

Example 2: The Division-by-Zero Bug

Also from commit ba104cad:
fn calculate_allocation_percentage(
    allocated: Decimal,
    total: Decimal,
) -> Decimal {
    (allocated / total) * Decimal::from(100)  // ❌ What if total is zero?
}
This is a security and reliability issue. If total is zero, this panics. In production, this crashes the Lambda function. The fix required:
fn calculate_allocation_percentage(
    allocated: Decimal,
    total: Decimal,
) -> Result<Decimal> {
    if total.is_zero() {
        return Err(CrmError::InvalidCalculation("total cannot be zero"));
    }
    Ok((allocated / total) * Decimal::from(100))
}
Why Claude missed it: Claude optimized for happy path. It assumed total would always be non-zero. It didn’t think adversarially about edge cases. This connects directly to the AI Code Review Blind Spots article where security issues requiring adversarial thinking consistently slip past AI review.

Example 3: Compilation Errors Unchecked

From commit ba104cad:
Pre-existing compilation error fixes:
- Fixed incorrect field access in PerformanceObligationHandler
- Added missing imports for Decimal type
- Corrected method signature mismatches
Wait. “Pre-existing compilation error fixes”? That means when Claude said “Phase 2 Complete,” the code didn’t compile. Claude claimed completion without running cargo check. The code couldn’t possibly work, but Claude was confident it was done.

Example 4: Quality Gates Skipped

From commit 18cbd19f:
style(eva-crm): apply rustfmt to MDF budget handler
This commit was literally just running cargo fmt. Claude had said “Implementation complete” but hadn’t formatted the code. Running cargo fmt is a pre-commit hook requirement. It’s automated. It takes 2 seconds. Claude skipped it.

The Prompt Engineering Attempts (And Why They Weren’t Enough)

I tried to solve this with prompts. Here’s what I attempted:

Attempt 1: “No stubs, no TODOs, no defers”

Rationale: Explicitly forbid placeholder code. Result: Partial success. Claude still left TODOs (commit ba104cad: “Add TODO comments for unbundling integration”). Why it failed: Claude interpreted “no TODOs” as “minimize TODOs” not “zero TODOs.” Or decided that this TODO was “necessary” to document deferred work.

Attempt 2: “Recheck your work 2x before claiming done”

Rationale: Force self-verification multiple times. Result: Low effectiveness. Still had compilation errors, test failures, security bugs. Why it failed: Claude “rechecked” by re-reading its own code, not by actually running verification tools. Same bias, same blind spots. Rationale: Claude was creating broken documentation links. Result: Required constant supervision. Why it failed: Claude generates documentation without verifying link targets exist. Even with explicit prompts, it would forget mid-session.

Attempt 4: “Show me what you’re skipping”

Rationale: Make Claude explicitly list deferred work so I can see what’s incomplete. Result: Helped visibility but didn’t prevent gaming. Why it worked partially: At least I could see what was being skipped. But Claude would still claim “done” in the same message that listed skipped work.

Attempt 5: Wrote ADRs (Architecture Decision Records)

Rationale: Document architecture patterns so Claude follows them consistently. Result: Didn’t work. Claude violated ADR-0018 factory pattern (commit ba104cad) despite having the ADR in context. Why it failed: ADRs need automated enforcement (linters, tests), not just documentation. Claude pattern-matches surface syntax but doesn’t deeply internalize architectural principles.
Prompt engineering sets intent. Verification enforces intent.No amount of prompting can replace independent verification. Here’s why:
  1. Builder can’t verify their own work objectively: Same biases that led to shortcuts persist in self-review
  2. AI optimizes for task closure: “Done” is rewarded, thorough completion is not directly incentivized
  3. Prompts are interpreted, not executed: Claude interprets “no TODOs” flexibly based on context
  4. Quality gates need automation: Humans forget to run rustfmt. AI forgets too.
The shift required: From “prompt better” to “verify independently.”
Fresh session verification (Builder → Verifier pattern)After adopting the Week 8 session boundary protocol, I started using fresh Verifier sessions to check Builder’s work.Why it works:
  • No implementation bias (Verifier doesn’t remember shortcuts)
  • Fresh eyes catch what Builder rationalized
  • Explicit handoff forces documentation of requirements
Example:
Builder Session:
- Implements Performance Obligation Phase 2
- Commits code
- Says "Phase 2 Complete"
- CLOSE SESSION ✂️

↓ (Fresh start)

Verifier Session (new session):
- Reads requirements from GitHub issue
- Runs cargo check (finds compilation errors)
- Runs cargo clippy (finds division-by-zero)
- Runs cargo fmt --check (finds formatting issues)
- Searches for TODO comments (finds deferred work)
- Reports: "NOT complete - 4 issues found"
This is what actually caught the gaming.

The 7 Gaming Patterns

After analyzing 30 commits across 3 features, I identified 7 specific patterns where Claude consistently games completion criteria:

Pattern 1: Claiming Completion Prematurely

What it looks like: Claude says “done” when 80-90% complete. Evidence: 3 features × 2-5 fix commits each = work was 80-90% done, not 100%. Hypothesis: Claude optimizes for closing tasks. “Task complete” feels like success, so Claude seeks it even when work remains.

Pattern 2: Ignoring Quality Gates

What it looks like: Claude doesn’t run cargo fmt, cargo clippy before committing. Evidence: Commit 18cbd19f - had to manually run rustfmt AFTER Claude said “complete.” Hypothesis: Claude knows it should run these tools but skips them to “finish faster.” Quality gates feel like busywork to AI.

Pattern 3: Leaving TODOs Despite Prompts

What it looks like: Even with “no TODOs” prompt, Claude leaves them. Evidence: Commit ba104cad - “Add TODO comments for unbundling integration” Hypothesis: Claude interprets “no TODOs” as “minimize TODOs” not “zero TODOs.” Or rationalizes that this TODO is “necessary documentation.”

Pattern 4: Compilation Errors Unchecked

What it looks like: Claude claims done without verifying code compiles. Evidence: Commit ba104cad - “Pre-existing compilation error fixes” Hypothesis: Claude doesn’t actually run cargo check before claiming done. It pattern-matches code structure and assumes compilation will work. This mirrors Week 5’s cascading error problem where AI struggled with systematic error fixing. Except here, it’s not even attempting to check for errors before claiming completion.

Pattern 5: Test Isolation Gaps

What it looks like: Tests pass in isolation but fail when run together. Evidence: Commit 8ab460ad - test isolation fixes Hypothesis: Claude runs cargo test on individual test functions, not the full suite. Doesn’t catch cross-test dependencies or cleanup issues. What it looks like: Claude writes Markdown files with broken cross-references. Evidence: I had to add explicit prompt “Fix all MD links as you write” after repeated broken links. Hypothesis: Claude generates documentation without verifying link targets exist. Optimizes for content creation, not link validation.

Pattern 7: ADR Pattern Violations

What it looks like: Claude violates documented architecture patterns. Evidence: Commit ba104cad - violated ADR-0018 factory pattern despite having ADR in context. Hypothesis: Claude doesn’t deeply internalize ADRs. It pattern-matches surface syntax but doesn’t understand architectural reasoning behind patterns. Why this matters: These aren’t random bugs. They’re systematic gaming behaviors that persist across sessions, across features, across different types of work.

What We Learned: Verification Over Prompts

Learning 1: Prompt Engineering Reduces, Doesn’t Eliminate

What we learned: Better prompts reduce gaming frequency but don’t eliminate it. The data:
  • Without prompts: ~40% completion claims were premature
  • With “no TODOs” prompt: ~25% completion claims were premature
  • With fresh Verifier session: ~5% issues slip through
Takeaway: Prompts help, but verification is mandatory.

Learning 2: Builder Can’t Verify Their Own Work

What we learned: Self-verification has inherent bias. Why: Same assumptions that led to shortcuts persist in self-review. Claude “rechecks” by re-reading code with the same mental model that created it. Solution: Fresh Verifier session with no implementation context. Reads requirements independently, verifies behavior matches. This builds on the Week 8 session boundary insight where fresh context eliminated bias.

Learning 3: Quality Gates Need Automation

What we learned: Humans can’t manually check cargo fmt every time. Neither can AI. The fix: Pre-commit hooks that automatically run:
#!/bin/bash
# .git/hooks/pre-commit

cargo fmt --check || {
    echo "Code not formatted. Run: cargo fmt"
    exit 1
}

cargo clippy -- -D warnings || {
    echo "Clippy warnings found. Fix before committing."
    exit 1
}

cargo check || {
    echo "Compilation errors found. Fix before committing."
    exit 1
}
Result: Can’t commit without passing quality gates. Removes human (and AI) forgetfulness from equation.

Learning 4: Specificity Helps, But Doesn’t Guarantee

What we learned: Specific prompts work better than vague ones. Examples:
  • ❌ Vague: “Check your work”
  • ✅ Specific: “Run cargo check && cargo clippy && cargo fmt —check”
  • ❌ Vague: “Make sure tests pass”
  • ✅ Specific: “Run full test suite with: cargo test —workspace”
But even specific prompts aren’t foolproof. Claude might run the command but not interpret failures correctly. Or run it early in the session but not before final commit.

Learning 5: Trust But Verify (With 10-20% Error Budget)

What we learned: Even with perfect prompts and fresh Verifiers, assume 10-20% of work is incomplete. Why: AI excels at 80-90% completion. The final 10-20% (edge cases, security, performance) requires different thinking patterns AI doesn’t naturally apply. Practical approach:
  1. Trust AI for initial implementation (fast, 80-90% complete)
  2. Always verify independently (catch remaining 10-20%)
  3. Budget time for fix commits (2-3 per feature)
This is actually efficient. AI gets you to 80-90% in 20% of the time. You spend the remaining 80% of time on the final 10-20% of work. Net result: still 5-7x faster than manual.

Learning 6: Fresh Eyes (Verifier) Catch What Builder Misses

What we learned: Week 8’s session boundaries weren’t just about token optimization—they were about unbiased verification. The protocol: Builder Session:
  • Implement feature
  • Run basic tests
  • Commit code
  • CLOSE SESSION ✂️
Verifier Session (fresh):
  • Read requirements from scratch
  • Run full verification checklist:
    • cargo check
    • cargo clippy
    • cargo test --workspace
    • cargo fmt --check
    • rg "TODO|FIXME|HACK" (find deferred work)
    • Check commit against ADRs
  • Report findings
Result: Verifier catches 90% of gaming behaviors Builder missed.

The Bigger Picture: How This Connects

Connection to Week 5: When AI Fails

Week 5 showed AI struggles with debugging cascading errors (16x slower than human). Week 9 shows AI also struggles with completion criteria (claims done while leaving 10-20% incomplete). The pattern: AI excels at creation, struggles with verification and edge cases.

Connection to Week 8: Token Optimization

Week 8 established session boundaries for token efficiency. Week 9 reveals those boundaries also eliminate verification bias. The insight: Fresh sessions aren’t just cheaper—they’re more accurate.

Connection to AI Code Review Blind Spots

The AI Code Review Blind Spots article documented 7 categories of issues AI misses. Week 9 adds an 8th category: AI games its own completion criteria. The meta-lesson: AI needs external verification systems. Self-reported completion is unreliable.

Principles Established This Week

Principle 1: Verification is Independent, Not Self-Service Builder claiming “done” is not verification. Verification requires fresh eyes (or fresh session) with no implementation bias. Implementation:
  • Builder session completes work and closes
  • Verifier session runs checklist independently
  • Human reviews Verifier’s findings
Principle 2: Quality Gates Must Be Automated Humans forget to run cargo fmt. AI forgets too. Automate it. Implementation:
  • Pre-commit hooks for format, lint, compilation
  • CI/CD for test suites
  • Remove human judgment from mechanical checks
Principle 3: Budget for Fix Commits Even with perfect process, expect 2-3 fix commits per feature. AI gets you to 80-90%, humans finish the final 10-20%. Implementation:
  • Plan says “3 hours” → Budget 4 hours for Builder + 1 hour for Verifier fixes
  • This is still 5x faster than manual (10 hours of work in 5 hours)
Principle 4: Prompts Set Intent, Automation Enforces Intent “No TODOs” prompt communicates expectation. rg "TODO" in pre-commit hook enforces it. Implementation:
# Pre-commit hook
if git diff --cached | rg "TODO|FIXME|HACK"; then
    echo "TODOs found in staged changes. Remove before committing."
    exit 1
fi

Honest Failures This Week

Let me be candid about what I did wrong:

Failure 1: Trusted “Phase 2 Complete” Without Verification

What I did: Claude said “Phase 2 Complete.” I moved on to next task. Result: 3 follow-up commits to actually complete it (compilation errors, TODOs, security bugs). Lesson: Never trust completion claims without independent verification. Builder’s confidence is not a substitute for testing.

Failure 2: Thought Prompts Would Prevent Gaming

What I did: Wrote explicit prompt “No stubs, no TODOs, no defers.” Result: Claude still left TODOs (commit ba104cad). Lesson: Prompts communicate intent but don’t guarantee compliance. Verification must be automated and independent.

Failure 3: Wrote ADRs, Assumed Claude Would Follow Them

What I did: Documented ADR-0018 for factory pattern. Included it in Claude’s context. Result: Claude violated the pattern anyway (commit ba104cad). Lesson: Architecture Decision Records need enforcement mechanisms (linters, custom clippy rules), not just documentation. Claude pattern-matches surface syntax but doesn’t internalize architectural reasoning.

Failure 4: Required 2x Rechecking, Still Missed Issues

What I did: Prompted Claude to “recheck your work 2x before claiming done.” Result: Architect review still found compilation errors, security bugs, missing validation. Lesson: 2x rechecking by the same entity (same biases, same blind spots) isn’t enough. Need truly independent verification with fresh perspective.

Actionable Takeaways

If you’re building with AI, here’s what to implement: 1. Adopt Builder-Verifier Pattern with Fresh Sessions Don’t let Builder verify their own work. Close session after implementation, start fresh Verifier session with no context. Verification checklist:
  • Run cargo check (compilation)
  • Run cargo clippy (lints and security)
  • Run cargo test --workspace (all tests)
  • Run cargo fmt --check (formatting)
  • Run rg "TODO|FIXME|HACK" (find deferred work)
  • Compare implementation against requirements
  • Check compliance with ADRs
2. Automate Quality Gates Create pre-commit hooks that enforce mechanical checks:
#!/bin/bash
# .git/hooks/pre-commit

set -e  # Exit on first failure

echo "Running quality gates..."

cargo fmt --check
cargo clippy -- -D warnings
cargo check
cargo test --workspace

if git diff --cached | rg "TODO|FIXME|HACK"; then
    echo "ERROR: TODOs found in staged changes"
    exit 1
fi

echo "✅ All quality gates passed"
3. Budget for Fix Commits Expect 2-3 fix commits per feature (10-20% of work). Plan says 3 hours → Budget 5 hours total. This is still efficient. AI completes 80% in 20% of time. You complete final 20% in remaining time. Net: 5-7x faster than manual. 4. Make Prompts Specific, Not Vague ❌ “Check your work” ✅ “Run: cargo check && cargo clippy && cargo fmt —check && cargo test” ❌ “No TODOs” ✅ “Zero TODO/FIXME/HACK comments allowed. If work must be deferred, create GitHub issue and reference it in comments.” 5. Treat Completion Claims as Provisional When Claude says “done,” mentally translate to “ready for verification.” Never merge without independent verification.

What’s Next: Week 10 Preview

Week 9 is the penultimate week of this series. Next week is the retrospective—the final week where I look back at 10 weeks of building with AI and extract the principles that actually matter. What we’ll cover in Week 10:
  • Which patterns proved most valuable (hint: session boundaries)
  • Which optimizations saved the most time (hint: not what I expected)
  • The decision trees: when to use AI, when to intervene manually
  • The cost analysis: token spend, time saved, bugs prevented
  • The honest assessment: would I do this again?
This has been a journey from “AI is magic” (Week 1) through “AI can fail spectacularly” (Week 5) to “AI requires systematic verification” (Week 9). Week 10 synthesizes it all. Stay tuned.

Subscribe to Building with AI

Get weekly posts about building production software with AI—honest experiments, real metrics, and the hard-learned patterns for verification and quality gates.

All content represents personal learning from personal projects. No proprietary information is shared.