Skip to content
Santekno.com | Level Up Your Engineering Skills
EN
📖 0%
01 Sep 2026 · 10 min read ·Article 35 / 208
Go

Debugging Workflow Spec Kit: When Claude Skips a Spec Instruction

How to detect and fix cases where Claude Code doesn't follow spec instructions in GitHub Spec Kit. A debugging workflow, spec audit, and enforcement strategies for Golang projects.

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

Debugging Workflow: When Claude Skips a Spec Instruction

Even with a structured debugging workflow in Spec Kit for Golang, Claude Code sometimes produces an implementation that differs from the specification. This is not an AI failure — it is exactly the human oversight point we rely on to keep quality high. What matters is having a repeatable process to detect, debug, and prevent spec deviation before it reaches main.

This article covers the five kinds of deviation, the specify audit tooling that surfaces them, concrete Go-level fixes, and the upstream changes that stop deviation from recurring.


15.1 The Types of Spec Deviation

Before debugging, you need a vocabulary for what can go wrong. Deviation shows up in five recognizable shapes, and the enumeration below names each one with a concrete example.

text
 11. Missing implementation
 2   Spec:    AC13 — rating outside 1-5 → 400 Bad Request
 3   Reality: no rating validation anywhere
 4
 52. Wrong implementation
 6   Spec:    AC14 — not purchased → 422 PURCHASE_REQUIRED
 7   Reality: handler returns 403 "FORBIDDEN" (wrong status + code)
 8
 93. Scope creep
10   Spec:    only GET, POST, DELETE for reviews
11   Reality: Claude also adds an unspecified PATCH endpoint
12
134. Architecture deviation
14   Spec:    repository must return domain errors, not DB errors
15   Reality: repository returns pgx.ErrNoRows straight to usecase
16
175. NFR deviation
18   Spec:    p95 response time < 200ms
19   Reality: unindexed query, p95 ~800ms in production

The takeaway: naming the deviation type tells you where to look. Missing and wrong implementations live in the code path for an AC; architecture and NFR deviations point back to the constitution and the plan.


15.2 The Primary Tool: specify audit

The first line of defense is specify audit, which checks the implementation against every AC and EC. The session below shows it catching a single missing validation among sixteen criteria.

bash
 1specify audit --feature product-review
 2
 3# 🔍 Auditing feature: product-review
 4# ✅ AC1–AC11: all verified
 5# ❌ AC12: Content length validation — NOT FOUND
 6#          Expected: validation for max 1000 characters
 7#          Found: no content length check in handler or usecase
 8# ✅ AC13: PURCHASE_REQUIRED error code verified
 9# ✅ AC14: ALREADY_REVIEWED error code verified
10#
11# Summary: ACs 15/16 (93.8%) · Action required: fix AC12

The point: audit turns “did the AI follow the spec?” into a per-criterion pass/fail report. You get a precise, machine-checked list of exactly which acceptance criteria are unmet before a human ever reviews the code.


15.3 Localizing the Problem with –debug

Knowing an AC failed is only half the battle; you need to know where the fix belongs. The --debug flag pinpoints the file and function and even proposes code. The output below traces the missing AC12 validation to the domain layer.

bash
 1specify audit --feature product-review --debug AC12
 2
 3# Debugging AC12: Content length validation
 4# Spec says:  "Content empty or > 1000 chars → 400 Bad Request"
 5# Plan says:  Task 01 — Validation: Content max 1000 chars
 6#
 7# ❌ internal/domain/review/entity.go validate(): only checks non-empty
 8# ❌ internal/usecase/review/create_review.go: no length check
 9# ❌ internal/delivery/http/handler/review_handler.go: no length check
10#
11# Best fix location: domain entity validate() (canonical per Clean Architecture)

The takeaway: --debug collapses a hunt across three layers into a single recommendation, and it respects your architecture — it points at the domain entity, the canonical place for validation, rather than patching the handler.


15.4 Applying the Fix in Go

With the location identified, the fix itself is small and idiomatic. The domain validate() method below adds the missing length boundary the spec requires.

go
 1func (r *Review) validate() error {
 2    if len(r.Content) == 0 {
 3        return errors.New("content is required")
 4    }
 5    if len(r.Content) > 1000 { // ← the missing AC12 check
 6        return ValidationError{
 7            Field:   "content",
 8            Message: "content must be at most 1000 characters",
 9        }
10    }
11    return nil
12}

After patching the entity, re-run the audit to confirm the criterion now passes and nothing else regressed — a green ACs: 16/16 (100%) is the signal you are done. The lesson: fix at the canonical layer, then let the audit, not your intuition, certify the fix.


15.5 Error Code Case Mismatch

One of the most frequent deviations is subtle: the right status code with the wrong error string. The comparison below shows a handler that returns 422 correctly but emits purchase_required where the spec demands PURCHASE_REQUIRED.

text
1Spec:      AC13 — not purchased → 422 PURCHASE_REQUIRED
2Generated: HTTP 422 ✅  but  {"error": "purchase_required"}  ← case mismatch

Audit catches this because it greps for the exact spec string, but the durable fix is to stop using string literals altogether. The Go below centralizes the codes as constants so a typo becomes a compile-time impossibility.

go
 1// internal/domain/errors/codes.go
 2const (
 3    ErrCodePurchaseRequired = "PURCHASE_REQUIRED" // exact match with spec
 4    ErrCodeAlreadyReviewed  = "ALREADY_REVIEWED"
 5    ErrCodeOrderNotFound    = "ORDER_NOT_FOUND"
 6)
 7
 8// Handlers always use the constant, never a literal:
 9c.JSON(http.StatusUnprocessableEntity, map[string]string{
10    "error": domain.ErrCodePurchaseRequired, // can't be mistyped
11})

The takeaway: string literals invite case drift; constants make the spec’s error codes the single source of truth. This one pattern eliminates an entire recurring class of deviation.


15.6 Architecture Deviation: Leaking DB Errors

A more serious deviation violates the constitution rather than a single AC. The example below shows a repository leaking pgx.ErrNoRows up to the usecase — a Clean Architecture violation — next to the version the spec expects.

go
 1// ❌ Deviation: leaks a DB-level error into the domain
 2func (r *reviewRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Review, error) {
 3    var review domain.Review
 4    err := r.pool.QueryRow(ctx, query, id).Scan( /* ... */ )
 5    if err == pgx.ErrNoRows {
 6        return nil, pgx.ErrNoRows // wrong: pgx error crosses the layer
 7    }
 8    return &review, err
 9}
10
11// ✅ Correct: return (nil, nil) for not-found, wrap real errors
12func (r *reviewRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Review, error) {
13    var review domain.Review
14    err := r.pool.QueryRow(ctx, query, id).Scan( /* ... */ )
15    if err == pgx.ErrNoRows {
16        return nil, nil // not-found is nil, nil
17    }
18    if err != nil {
19        return nil, fmt.Errorf("reviewRepository.GetByID: %w", err)
20    }
21    return &review, nil
22}

This kind of deviation happens because the constitution was not explicit enough. The fix is upstream — the constitution rule below states the pattern in terms the AI cannot misread.

markdown
1## Repository Error Handling
21. Not found → return (nil, nil), NOT pgx.ErrNoRows
32. DB error  → return (nil, fmt.Errorf("context: %w", err))
43. NEVER return pgx errors to the usecase layer
5✅ return nil, nil                                   // not found
6✅ return nil, fmt.Errorf("...GetByID: %w", err)     // wrapped
7❌ return nil, pgx.ErrNoRows

The takeaway: an architecture deviation is a signal that the constitution is too vague. Tightening the rule — with explicit correct and incorrect examples — fixes the whole class, not just this one repository.


15.7 Scope Creep Detection

Sometimes Claude implements more than the spec asked for. The --check-scope flag flags endpoints and files that have no backing AC. The output below catches an unspecified PATCH endpoint.

bash
1specify audit --feature product-review --check-scope
2
3# ✅ POST   /products/:id/reviews             — in spec (AC1)
4# ✅ GET    /products/:id/reviews             — in spec (AC6)
5# ✅ DELETE /products/:id/reviews/:review_id  — in spec (AC8)
6# ⚠️  PATCH  /products/:id/reviews/:review_id  — NOT in spec!
7# ⚠️  internal/usecase/review/update_review.go — not in tasks.md!
8#
9# Recommendation: add an AC and approve, or remove the files

The takeaway: scope creep is not automatically wrong, but it must be a decision, not an accident. Either formalize the extra behavior with a new AC and approval, or delete it — never let unspecified code merge silently.


15.8 The Subtle Case: DoD Passes but Behavior Is Wrong

The hardest deviation to catch is when every test passes but the behavior is still wrong. The scenario below shows a rating calculation that passes only because the test data happens to divide evenly.

text
1Spec: AC10 — rating recalculated after delete
2Test: TestDeleteReview_RecalculatesRating — PASS
3
4Reality: average uses integer division: (4+5)/2 = 4 (should be 4.5)
5Test passes because test data has an even average.
6Production fails because real data usually doesn't.

The cure is a stronger test that exercises non-even values. The Go below replaces the weak assertion with one that would actually fail on the integer-division bug.

go
1// ✅ Stronger: forces a decimal average the weak test never hit
2func TestDeleteReview_RecalculatesRating_PreservesDecimal(t *testing.T) {
3    // reviews rated 3, 4 → avg 3.5
4    assert.InDelta(t, 3.5, product.AverageRating, 0.01)
5}

The takeaway: a green Definition-of-Done is necessary but not sufficient. Behavioral deviation hides behind weak assertions, so strengthen tests with boundary and non-even values — then add that requirement to the task’s DoD so it is enforced next time.


15.9 Prevention: Fix Upstream, Not Just the Code

Debugging is reactive; the real win is preventing deviation at the source. Most recurring deviations trace back to a vague spec, a weak DoD, or an under-specified constitution. The block below contrasts ambiguous and explicit wording for each.

markdown
 1# AC wording
 2❌ "Invalid content → return error"
 3✅ "Content > 1000 chars → HTTP 400, {"error": "INVALID_CONTENT"}"
 4
 5# DoD item
 6❌ "- [ ] Content validation works"
 7✅ "- [ ] curl POST 1001-char content → 400 {"error": "INVALID_CONTENT"}"
 8✅ "- [ ] curl POST 1000-char content → 201 (boundary, valid)"
 9
10# Constitution rule
11✅ "Error codes MUST be UPPERCASE_SNAKE_CASE: PURCHASE_REQUIRED not purchase_required"

The takeaway: precision upstream is cheaper than debugging downstream. An explicit AC, a concrete DoD, and a specific constitution rule remove the ambiguity that produced the deviation in the first place.


15.10 Enforcing a Quality Gate in CI

Finally, make deviation impossible to merge by wiring the audit score into CI. The step below fails the build when the audit score drops below the team’s threshold.

yaml
1# .github/workflows/spec-audit.yml
2- name: Spec audit quality gate
3  run: |
4    SCORE=$(specify audit --feature $FEATURE --score --format number)
5    if [ "$SCORE" -lt "90" ]; then
6      echo "❌ Spec audit score $SCORE < 90 (minimum)"
7      exit 1
8    fi
9    echo "✅ Spec audit score: $SCORE/100"

The takeaway: a numeric quality gate turns spec compliance into a hard merge requirement — deviation cannot reach main without a human explicitly lowering the bar. Self-check locally before raising the PR, and the gate rarely trips.


15.11 The Debugging Loop

Everything above composes into a single iterative loop. The diagram below shows how detection feeds back into either the spec or the implementation until the audit passes.

text
1Spec → Implement → Audit
2  ↑                  │
3  │              Deviation found
4  └── Fix spec ←─────┤
5      (if ambiguous)  │
6                      └── Fix implementation
7                              └→ Re-audit → PASS

The point: deviation is normal and expected — the value is in the loop that catches and corrects it. When the root cause is an ambiguous spec, you fix the spec; when it is genuine implementation error, you fix the code; either way, you re-audit until it is green.


15.12 Summary

Spec deviation is a natural part of AI-assisted development — the AI is not perfect, specs are not always clear, and judgment calls happen during implementation. What keeps quality high is the process to detect and fix it:

  • specify audit reports per-AC compliance; --debug AC{n} localizes the fault to a specific file and function.
  • Recurring deviations — error-code case mismatch, leaked DB errors, weak tests — are best killed with Go-level patterns (constants, (nil, nil) for not-found, boundary tests).
  • The root cause is almost always upstream: an ambiguous spec, a stale CLAUDE.md, or a weak DoD. Fix it there, not just in the code.
  • A CI quality gate ensures deviation cannot merge undetected.

Next, we move into Part 4 — how Spec Kit scales beyond a single developer to larger teams: onboarding, a shared constitution, and structured collaboration.

Related Articles

💬 Comments