Skip to content

Instantly share code, notes, and snippets.

@aviadr1
Created March 26, 2026 21:59
Show Gist options
  • Select an option

  • Save aviadr1/7246852923afe08bf61932c784085e76 to your computer and use it in GitHub Desktop.

Select an option

Save aviadr1/7246852923afe08bf61932c784085e76 to your computer and use it in GitHub Desktop.
Planning Quality Research — Evidence: 23 Concrete GitHub Failure Cases, 9 Categories

Extended GitHub Evidence: Issue→Plan Quality Failures in Garsson-io/kaizen

Forensic research pass. Excludes the 8 already-documented cases (PR #832/#666, PR #816/#814, PR #894/#891, PR #970/#966, issues #940/#957, issue #724, issue #901).


Additional Cases (15+ examples)


Case 9 — PR #708 / Issue #685: Symptom-Targeted Fix Instead of Failure Class Fix

Category: Hypothesis-as-Contract (existing)

What was planned: Issue #685 prescribed a static lint hook (kaizen-lint-kernel-paths.sh) that blocks writes to test files containing /proc/, /sys/, process.kill, etc. PR #708 implemented it fully: 22 tests, hook registered, all passing.

How the plan was inadequate: The issue addressed one specific trigger (/proc/invalid/path hanging on WSL2) rather than the failure class ("tests hang with no circuit breaker"). The proposed solution defended against a threat that wouldn't recur. The correct fix was test timeouts (#684) and a per-run wall-clock budget (#686) — which would handle any hang, not just /proc hangs.

Result: PR #708 merged. Issue #712 filed ("Revert kernel-path lint hook — wrong abstraction level") by admin. PR #736 reverted it entirely (232 lines deleted, zero added). The correct fixes (#684, #686) were implemented separately.

Was the information available? Yes. The issue body itself said "Root cause: no test timeout." The issue also referenced #684 (vitest testTimeout) and #686 (wall-clock budget) as companion work. The plan builder focused on the incident trigger, not the failure class those issues addressed.

Failure signature: Issue prescribes a specific mechanism ("add a hook that blocks X") rather than stating the failure mode. Admin later says "wrong abstraction level" in the revert issue body.


Case 10 — PR #803: Deletion Without Tracing Dependents

Category: Incomplete Scope (new — see Category 6 below)

What was planned: Issue #790 asked to delete state-utils.sh after all consumers migrated to TypeScript. PR #803 deleted it plus two dead bash hooks, their tests, and updated docs. Net: -1575 lines.

How the plan was inadequate: The plan scoped deletion correctly but did not check whether any bash test files referenced the deleted hooks without consuming their logic directly. Six bash test files (test-pr-kaizen-clear.sh, test-hook-interaction-matrix.sh, test-integration-kaizen-lifecycle.sh, test-waiver-quality.sh, test-kaizen-merge-gate.sh, test-integration-audit-isolation.sh) had require_file guards pointing at the deleted hooks. When the hooks were gone, the guards silently skipped all 3054 lines of tests — resulting in false green CI with zero assertions executing.

Result: Issue #808 filed: "3054 lines of bash tests silently skip after TS migration — false green on hook integration coverage." PR #809 deleted the six dead test files. The integration scenarios those tests covered (cross-hook interaction invariants, full lifecycle tests) had no TS equivalent — the coverage loss was accepted.

Was the information available? Yes. A pre-deletion grep -r "kaizen-pr-reflect-clear" .claude/hooks/tests/ would have revealed all 6 files. The standard deletion checklist (update plugin.json, settings.json, doc references) was followed; "grep for test files that guard on this hook" was not.

Failure signature: Deletion PR checks "all tests pass" but doesn't ask "are any tests silently skipping?"


Case 11 — PR #710 / Issue #814: Disable-Instead-of-Fix (Partial)

Category: Hypothesis-as-Contract (existing)

What was planned: Issue #814 said "fix the underlying slowness rather than increasing the timeout." The timing sentinel (runHookTimingSentinel()) benchmarked all hooks by running each bash wrapper, and with 10+ wrappers each spawning npx tsx (1-2s each), the total exceeded the 10s test timeout. Root cause: sentinel was slow; it benchmarked every hook on every test run.

How the plan was inadequate: PR #816 disabled the sentinel in test environments via an env var (HOOK_TIMING_SENTINEL_DISABLED). The sentinel still runs in production. The acceptance criterion — "fix the underlying slowness" — was explicitly not met. The admin-stated requirement and the delivered implementation were directly at odds.

Was the information available? Yes. The issue body stated the acceptance criterion explicitly. The root cause (npx tsx startup time) was identified in the PR body. An alternative approach (benchmark only a subset of hooks, cache results, run asynchronously) was not explored.


Case 12 — PR #718 → PR #720: Over-Correction Requires Immediate Corrective PR

Category: Dual Failure Mode Blindness (new — see Category 7 below)

What was planned: PR #718 fixed the "multi-pick loop" in auto-dent where agents kept picking new issues after completing one (run-2 picked 4 issues in one session). Fix: "one issue per run" policy.

How the plan was inadequate: The fix was correct in spirit but wrong in mechanism. /kaizen-deep-dive already had a legitimate bundling capability for related issues. The "one issue per run" rule prevented intentional bundling that was valid workflow. The plan builder focused on eliminating the observed failure mode without asking "what valid behavior does this rule prevent?" — the dual failure mode check prescribed in issue #722.

Result: PR #720 filed immediately after #718 ("fix(auto-dent): plan-first scope — intentional bundling, not blind loop"). PR #720 replaced the rule with a PLAN→EXECUTE→STOP pattern that allowed deliberate bundling while preventing reactive scope expansion.

Was the information available? Yes. The /kaizen-deep-dive skill explicitly documented intentional bundling. Issue #722 ("kaizen-implement: name both failure modes before shipping behavioral/prompt constraints") was filed during this same session, documenting that the agent didn't check both failure modes before shipping.

Failure signature: An L1 constraint fix requires a corrective PR within the same batch. Admin notes that the constraint "was too restrictive."


Case 13 — PR #846: Untracked Features Shipped in Review Battery

Category: Scope Leakage (new — see Category 8 below)

What was planned: Issue #849 asked for an adversarial review battery (compare PRs against linked issues). PR #846 delivered the core review battery — plus 4 extra review dimensions (plan-fidelity, dry, test-plan, improvement-lifecycle), a /kaizen-dimensions skill, docs/artifact-lifecycle.md, docs/review-dimensions-research.md, and 3 extra zen principles. None of these extras had corresponding issues.

How the plan was inadequate: The plan had no scope boundary. Work that emerged during implementation got bundled into the PR without lifecycle tracking. Three retroactive issues were filed (#861, #862, #863) just to document what had shipped.

Follow-on costs:

  • #861: track /kaizen-dimensions skill (shipped untracked)
  • #862: track 4 extra review dimensions (57% more review cost than planned)
  • #863: track 3 docs and 3 zen principles
  • #876: DRY/tooling follow-up from review battery review (3 post-review bugs: #881, #882, #883)
  • The 4 unplanned dimensions never had replay tests against motivating PRs (tracked in #860, #868)

Was the information available? The problem is structural: no scope boundary was declared before implementation. There was nothing to compare against.

Failure signature: Multiple issues filed retroactively to track what was shipped. Comment on issue: "shipped in PR #846, filed retroactively for lifecycle tracking."


Case 14 — PR #894 Post-Hoc Audit: 5 Bugs in Untestable Integration Code

Category: Testability Cutout (existing, extends Case from PR #894/#891)

This is a distinct sub-case from the already-documented PR #894/#891 case, involving the code quality of what was shipped.

What was planned: Issue #891 asked to wire the review-fix loop into auto-dent-run.ts. PR #894 did so, adding ~70 lines of integration code inside main().

How the plan was inadequate: The plan did not include extracting the integration into a testable function first. The wiring code embedded deep inside a 2000-line main() with no dependency injection — untestable without running the full system. Five bugs were discovered by post-hoc audit (#914 meta-issue):

  1. Fix loop spawned with 0 gaps when all dimensions timeout (#897)
  2. Budget uses total budget, not remaining (#898)
  3. Event emission boilerplate repeated 4x (#899)
  4. resume: false hardcoded, prevents crash recovery (#900)
  5. Zero unit test coverage for the wiring (#896)

Result: Issue #914 documented the pattern. PR #918 extracted runReviewWiring() with dependency injection and fixed all 5 bugs.

Was the information available? Yes. The issue body for #891 explicitly required "Portable zippable run artifacts" and "Test harness supports resume-from-failure." The test plan called for 5 unit tests. Only 4 event schema tests were delivered. The wiring was untestable as written — extracting it into a testable function was the prerequisite step that wasn't done.

Failure signature: Test plan promises unit coverage; delivered PR has only event schema tests. Post-hoc audit reveals bugs in the untested integration path.


Case 15 — PR #965 / Issue #963: Policy-Violating PR for a Policy About Violations

Category: Self-Referential Compliance Failure (new, closely related to Hypothesis-as-Contract)

What was planned: Issue #963 established Policy 10: skill changes require behavioral proof (before/after claude -p evidence + smoke test). PR #965 implemented Policy 10 enforcement.

How the plan was inadequate: PR #965 merged without the review battery running (#973). A retrospective review found 5 MUST-FIX items:

  1. No plan stored in structured data (plan-fidelity gap)
  2. No actual claude -p before/after transcripts (the very thing the policy requires)
  3. Duplicate imports in test file (dry)
  4. Worktree path resolution broken in tests
  5. No behavioral test for the new dimension

PR #974 was filed as a corrective to address all 5 items. The PR that introduced a policy requiring behavioral proof shipped without behavioral proof.

Was the information available? Yes — the policy text was written in the PR body itself. The review battery gate not firing was a separate bug (#973), but the plan builder should have verified the gate would fire before merging.

Failure signature: A policy-enforcement PR violates the policy it introduces. Corrective PR follows immediately.


Case 16 — Issue #758: Scope-Guard Deadlock — Fix Blocks the Fix

Category: Single Design (existing) + Self-Defeating Mechanism

What was planned: scope-guard.sh was designed to detect double-installation and block. When it detected kaizen@kaizen in ~/.claude/settings.json, it blocked all tool calls.

How the plan was inadequate: The scope-guard blocked ALL tools — including the Bash tool needed to fix the settings.json. This created a complete deadlock: the fix required Bash access; the guard blocked Bash access. The plan considered only the detection case, not the recovery case.

Result: Real user spent 10+ messages in a deadlocked session, eventually had to run a fix manually outside Claude Code (#757 incident). The guard had to be redesigned to allow the specific fix operation.

Was the information available? Yes. Designing a blocking guard requires asking "can the agent escape when this fires?" That question is not recorded as having been asked during the original design.

Failure signature: Admin discovers deadlock when a real user files an incident report. Issue body says "took 10+ messages to resolve, user had to manually run python3 outside Claude Code."


Case 17 — Issue #808 → PR #809: Migration Left 3054 Dead Test Lines

(Distinct from Case 10 — this captures the evaluation failure in the migration series, not just PR #803)

Category: Codebase Survey Failure (existing)

What was planned: The TS migration series (#786, #789, #798, #801, #803, #804) migrated bash hooks to TypeScript. Each PR ran "all tests pass" and reported correct counts.

How the plan was inadequate: No PR in the migration series checked "are any tests now silently skipping?" The bash test infrastructure uses require_file guards that gracefully skip when the targeted hook is absent — this is the correct behavior for optional hooks, but it creates invisible failures when a hook is deleted. The cumulative effect: 3054 lines of bash tests silently skipping after #803.

What "all tests pass" concealed: 6 test files reporting "0 passed, 0 failed, 0 skipped" when they were actually skipping every assertion. The CI summary showed no failures.

Was the information available? Yes. The require_file pattern was in every test file header. Any review of the test infrastructure would reveal that it gracefully skips on missing hooks. The migration plan should have included: "for each hook being deleted, grep for bash test files that guard on it and decide: port to TS or delete."


Case 18 — PR #965 Merged Without Review Gate (#973)

Category: Command-Detection-Not-Outcome Enforcement (maps to Category 6)

What was planned: pr-review-loop.ts PostToolUse hook creates a needs_review gate whenever gh pr create is detected in the Bash command. This is supposed to block merge until a review runs.

How the plan was inadequate: PR #965 used a large heredoc body for gh pr create. The command string contained raw newlines, backticks, dollar signs, and special Unicode. When Claude Code serialized this to JSON for the hook stdin, JSON.parse() threw. readHookInput() returned null. The hook exited silently at line 437 with process.exit(0) — no trace, no gate, no review.

Result: PR #965 merged without any review battery firing. Issue #973 filed (incident report). Issue #975 filed (null-input exits silently). PR #976 fixed the silent exit by adding a trace entry and logging raw bytes on parse failure.

Was the information available? The null-input path was discoverable by reading hook-io.ts. The process.exit(0) at line 437 had no trace call before it — visible in the code. No test existed for the null-input path.

Failure signature: Hook fires correctly on simple commands but silently fails on commands with special characters in --body arguments. The failure leaves no trace.


Case 19 — Issue #726 / Auto-Dent Duplicate Tracking Issues

Category: Codebase Survey Failure (existing) + Missing State Check

What was planned: Auto-dent batch batch-260323-1405-5400 created issue tracking. The harness called gh issue create for the batch tracking issue.

How the plan was inadequate: The harness did not check for an existing tracking issue before creating a new one. Two identical issues (#707, #709) were created with identical titles, splitting batch progress across two issues.

Result: Issue #726 filed. PR #825 fixed it by adding findExistingProgressIssue() — a search before create.

Was the information available? Yes. The "check before create" pattern is standard practice for idempotent issue creation. The harness code called gh issue create unconditionally.


Case 20 — Issue #843: 25 PRs Shipped with Zero Review Gate Firing

Category: Untested Seam (maps to existing + extends to test pyramid)

What was planned: The review gate system (pr-review-loop.ts PostToolUse hook) was supposed to fire whenever gh pr create was called in headless -p mode during auto-dent runs.

How the plan was inadequate: The gate was never verified to work in headless mode. Unit tests existed for the TypeScript functions. No E2E test existed that proved Claude Code would invoke the hooks, deliver output to the agent, and block the session. Batch jolly-marsupial ran 36 sessions and produced 25 PRs — zero review gates fired.

Investigation revealed: Hooks fire from the worktree, not the main checkout. Hook wrappers installed in the main checkout aren't invoked from worktree sessions. The entire enforcement chain was invisible in the mode where it mattered most.

Result: Issue #843 filed. The review battery was then built to work differently — spawning explicit claude -p subagents from the skill rather than relying on hooks to enforce review.

Was the information available? Yes. The hook system is invocation-point-sensitive. A single canary test (write to /tmp/file in a PostToolUse hook, start a headless session, check if the file was written) would have confirmed or denied hook firing in -p mode.

Failure signature: "All unit tests pass" combined with "zero enforcement events in production runs."


Case 21 — Issue #939 / PR #956: Five Systems Broke When Worktree Was Deleted

Category: Single Design (existing) — Shared Unstated Assumption

What was planned: Five independent systems (vitest.config.ts, review-fix.ts stateDir(), worktree-du.ts cleanupWorktrees(), gate-manager.ts, kaizen-worktree-setup.sh) were each built independently before worktrees became a core primitive.

How the plan was inadequate: Each system was planned and built in isolation. None of them surfaced the shared assumption: "the working directory is stable, long-lived, and owned by a single session." Worktrees violate all three. When a worktree is deleted after merge, five systems fail simultaneously in a cascade.

Symptoms:

  • vitest crawled 94 worktrees, polluting test output
  • review-fix.ts state stored in process.cwd()/.claude/review-fix/ was deleted with the worktree
  • worktree-du.ts deleted worktrees unconditionally with no drain or warning
  • Post-merge stop gate message omitted /kaizen reflection step
  • Sessions inside deleted worktrees had all tool calls fail (CWD no longer exists)

Was the information available? Yes. Once worktrees were established as a core primitive (.claude/worktrees/, kaizen-worktree-setup.sh), every new system touching CWD or file paths should have been audited for worktree safety. The audit happened reactively (issue #939) rather than proactively.


Case 22 — Issue #966 / PR #970: Skill Missing a Mandatory Step That Was Central to Its Purpose

Category: Goal vs. Work-Item (existing)

What was planned: The /kaizen-review-pr skill was built to run a review battery and post findings. The skill's SKILL.md described running dimension subagents and classifying findings.

How the plan was inadequate: The SKILL.md had no instruction to call store-review-finding after Phase 3. The review findings existed in-context but were never persisted to the PR. Three consecutive PRs (#933, #956, #965) had zero per-dimension finding comments. The storage step — the mechanism that makes reviews visible and resumable — was completely absent from the skill.

Divergent paths discovered: review-fix.ts CLI (the automated path) called storeReviewFinding() after reviewBattery() — hardcoded in the script. The skill (the manual path) had no such instruction. Two paths, two outcomes, zero consistency.

Was the information available? Yes. The store-review-finding CLI command existed and was documented. review-fix.ts called it. Reading both the skill and the CLI script side by side would have revealed the gap immediately. The skill was written without verifying it produced the same outputs as the automated path.


Case 23 — Issue #781: Admin Corrections Not Persisted — Feedback Lost Between Sessions

Category: Recursive Failure (new — see Category 9 below)

What was planned: The kaizen reflection skill had an "Admin teaching embedding" section that said to save corrections as memory files AND codify them in infrastructure.

How the plan was inadequate: The instruction was L1 only. No hook verified that when a correction was given, it was saved. The "write real E2E tests" feedback was given across multiple sessions but never saved as a memory or filed as an issue until the admin explicitly said "can you debug why the lesson on testing is not sticking?" The reflection produces a KAIZEN_IMPEDIMENTS declaration but doesn't force memory saves.

Was the information available? Yes. The reflection skill itself documented the required behavior. The gap was that the required behavior was L1 (optional) — no enforcement verified it happened. This is the system's own self-improvement mechanism failing to capture lessons about itself.

Failure signature: Admin says "I told you" or "how many times" — the signal that a correction was given before but not absorbed.


Case 24 — PR #708 / Issue #685 (Second Perspective): Spec Prescribed Wrong Solution

Category: Solution Collapse (existing)

What was planned: Issue #685 specified "Static lint: block tests that write to /proc, /sys, or kernel interfaces." The issue prescribed the mechanism (lint hook), not the failure mode (tests hang with no circuit breaker). PR #708 implemented the prescribed mechanism faithfully.

How the plan was inadequate: The implementor (kaizen-implement) accepted the spec as ground truth. The spec had already collapsed from failure mode to solution. There was no evaluation step that asked "is this the right solution?" The evaluate skill at the time of PR #708 did not have the 5-question spec critique added by issue #714.

Post-hoc recognition: Issues #713 ("describe the failure mode, not the solution") and #714 ("critically evaluate spec from multiple angles") were filed during the same admin session that caught the kernel-path hook problem. Both document the underlying evaluation gap.


Case 25 — Issue #921 / PR #924-#926: Hook Gates Verify Commands, Not Outcomes

Category: Command-Detection-Not-Outcome (extends existing)

What was planned: Multiple gates (pr-review-loop.ts, bump-plugin-version.ts, pr-kaizen-clear.ts) used command detection as the trigger for state transitions. "If the agent ran gh pr diff, review happened." "If the agent ran git commit, the code is committed."

How the plan was inadequate: None of the gate designs asked "what evidence proves this action succeeded?" A gate that fires on command presence clears when the command runs, regardless of outcome. Concrete symptoms:

  • bump-plugin-version.ts committed but didn't push — PR creation failed with "branch ahead of remote" (#919)
  • Review gate cleared on gh pr diff without verifying dimension agents were spawned or findings stored (#920)
  • Reflection gate cleared on echo KAIZEN_IMPEDIMENTS: [] without checking substance

Was the information available? Yes. The PostToolUse hook receives the tool result (exit code, stdout, stderr). Every gate had access to outcome evidence. None used it. Issue #921 documents this as a "category error" that spans all L2 gates.


Category Validation and New Categories

Existing 5 Categories — Confirmed and Extended

Cat 1: Goal vs. Work-Item — Plan treats deliverable as checked box without verifying the goal is achieved.

  • New cases: #22 (review-pr skill missing storage step), #12 (one-issue-per-run over-correction)
  • Frequency: High. Appears whenever implementation matches spec language but misses intent.

Cat 2: Hypothesis-as-Contract — Plan accepts unvalidated hypothesis as design fact; testing would refute it.

  • New cases: #9 (kernel-path hook), #11 (disable timing sentinel), #24 (spec prescribed wrong solution)
  • Frequency: Very high. The most common failure mode across the history.

Cat 3: Single Design — Plan produces one design without exploring alternatives; the one design has a fatal flaw.

  • New cases: #16 (scope-guard deadlock), #21 (five systems, shared unstated assumption)
  • Frequency: Medium. Appears on architectural decisions; often expensive when it occurs.

Cat 4: Testability Cutout — Plan ships code that works but can't be verified by tests; bugs surface post-merge.

  • New cases: #14 (review-fix wiring in main()), #20 (25 PRs without review gate)
  • Frequency: High. Appears repeatedly in integration code and headless mode.

Cat 5: Codebase Survey Failure — Plan designs from first principles; existing tools or dependencies overlooked.

  • New cases: #10 (deletion without tracing dependents), #17 (migration left 3054 dead test lines), #19 (duplicate issue creation)
  • Frequency: High. Common in auto-dent autonomous runs.

New Categories Identified

Category 6: Incomplete Deletion Scope Deletion is scoped to the target artifact but not to everything that references it. The deleted thing is gone; the references remain, silently broken (tests that skip, docs pointing at deleted files, plugin.json with dead hook paths).

Signature: "All tests pass" after deletion. Later discovery that tests were silently skipping, docs were stale, or CI was running against deleted artifacts.

Cases: #10 (Case 10), #17 (Case 17), plus repeated minor instances in the TS migration series.

Why existing categories miss it: Cat 5 (codebase survey) covers failing to find existing tools before building. Cat 6 covers failing to trace dependents before removing. Different error, different query: not "what already does this?" but "what points at this?"


Category 7: Dual Failure Mode Blindness A constraint fix eliminates one failure mode but creates the symmetric opposite. The plan builder optimizes against the observed failure without naming both ends of the design space.

Signature: A behavioral rule fix is followed immediately by a corrective PR that loosens the rule. Admin notes the fix was "too restrictive" or "prevented valid behavior."

Cases: #12 (one-issue-per-run → plan-first scope), #16 (scope-guard blocking fix operations), issue #722 formally names this pattern.

The test that catches it: Before shipping a behavioral constraint, name both failure modes — "if absent: X" and "if present: Y" — and verify Y is acceptable.


Category 8: Scope Leakage Without Lifecycle Implementation ships features beyond what was tracked in any issue. The extras are coherent with the project but have no lifecycle (no feedback loop, no replay tests, no improvement path). Retroactive issues are filed to track what shipped.

Signature: Multiple retroactive issues filed after merge. PR diff contains substantially more new files than referenced in any issue. Review battery flags "21% unrequested" scope in scope-fidelity dimension.

Cases: #13 (PR #846: 4 extra dimensions, 1 extra skill, 2 docs, 3 zen principles), PR #903 scope-fidelity finding (21% unrequested, all justified post-hoc).

Why it matters: Untracked features have no feedback loop. If they're wrong, nobody knows to fix them. If they drift, nobody tracks the drift. The improvement lifecycle PR #876 filed (DRY/tooling follow-up) was itself triggered by finding untracked elements.


Category 9: Recursive Failure — The System Fails to Capture Lessons About Itself The kaizen reflection and memory system is supposed to capture lessons from admin corrections. When the reflection system itself fails to enforce lesson capture, the same mistakes recur. This is kaizen failing to kaizen itself.

Signature: Admin says "I told you" or "this is not sticking." Memory files exist for the lesson but were never populated. Reflection produced KAIZEN_IMPEDIMENTS but no hook verified memory was saved.

Cases: #23 (E2E test feedback not retained across sessions), issue #781 (admin corrections not persisted), issue #959 (skill chain verification discipline enforced as prose, never as phases).


Frequency Analysis

Category Case Count (all, incl. prior 8) Highest-Cost Instance
Hypothesis-as-Contract 6+ PR #816/#814 (CI broken, timing sentinel disabled)
Codebase Survey Failure 5+ PR #970/#966 (orchestrator vs. agent-stores design)
Testability Cutout 5+ Batch jolly-marsupial: 25 PRs, zero review gates
Goal vs. Work-Item 4+ PR #832/#666 (schema built, 0 SKILL.md files populated)
Single Design 4+ Issue #758 (scope-guard deadlock — complete system lockout)
Incomplete Deletion Scope 3+ PR #803 / Issue #808 (3054 dead test lines)
Dual Failure Mode Blindness 2+ PR #718 → PR #720 (immediate corrective PR)
Scope Leakage Without Lifecycle 2+ PR #846 (4 unplanned dims, 1 unplanned skill)
Recursive Failure 2+ Issue #781 (E2E lesson not sticking)

Most costly by downstream impact (fixes required + time lost):

  1. Testability Cutout — 25 PRs shipped without review, entire batch unverified. Required redesign of review infrastructure.
  2. Single Design (Scope-Guard Deadlock) — Real user lost 10+ messages, filed incident report, led to #757 and #758.
  3. Codebase Survey Failure (Custom Storage) — Led to architecture rework in PR #970, multiple corrective issues.

Most frequent (highest recurrence):

  1. Hypothesis-as-Contract — 6+ cases; appears in every batch where autonomous work runs without hypothesis testing.
  2. Codebase Survey Failure — 5+ cases; the default failure mode for autonomous agents designing new features.

Failure Signature Index

For each category, the earliest visible signal in GitHub history that the plan was going wrong:

Cat 1 (Goal vs. Work-Item):

  • Signal in PR body: implementation confirms "all acceptance criteria delivered" but one criterion requires behavioral change, not code change. Zero behavioral evidence in the PR.
  • Signal in issue: acceptance criterion uses a verb like "stop being" or "make X happen" rather than "implement Y."
  • Earliest detection: review battery requirements dimension finding MISSING for a criterion that isn't a code artifact.

Cat 2 (Hypothesis-as-Contract):

  • Signal in issue body: "likely cause" or "hypothesis" in root cause section, but no test described.
  • Signal in plan: solution designed to address the stated mechanism, not the failure class.
  • Signal in PR: test plan tests the mechanism works, not that the failure class is prevented.
  • Earliest detection: issue #948/#949 — admin added mandatory hypothesis validation phases to skills.

Cat 3 (Single Design):

  • Signal in plan: no "alternatives considered" section; single implementation described as "the fix."
  • Signal in PR body: no design decision record, no tradeoff statement.
  • Earliest detection: issue #979 — the 5-question design pause checklist: "is there more than one design that would work, and do they fail differently?"

Cat 4 (Testability Cutout):

  • Signal in PR: test plan says "unit tests" but integration code has no dependency injection.
  • Signal in PR: "wiring" or "integration" code lives inside main() rather than an extracted function.
  • Signal in review: tooling-fitness dimension flags "integration code in main() — untestable."
  • Earliest detection: issue #883 (launchFix/prefetch not injectable), PR #876 extraction of runFixLoop().

Cat 5 (Codebase Survey Failure):

  • Signal in plan: plan mentions "we will build X" where X already exists (e.g., write-attachment).
  • Signal in PR: new functions duplicate functionality of existing CLI commands.
  • Signal in review: DRY dimension flags "reimplements utilities already in X."
  • Earliest detection: issue #957 — "planning phase doesn't survey existing tools." Review battery tooling-fitness dimension catches this post-implementation.

Cat 6 (Incomplete Deletion Scope):

  • Signal in PR: "deleted X" but no verification that referencing files were updated.
  • Signal in test output: 0 passed, 0 failed (but silently skipping).
  • Earliest detection: grep "require_file " before deleting. No automated check existed until post-discovery of issue #808.

Cat 7 (Dual Failure Mode Blindness):

  • Signal in issue: solution section specifies a blocking rule without naming what the rule prevents.
  • Signal in PR: test plan only tests that the bad case is blocked; no test that the good case still works.
  • Earliest detection: issue #722 ("name both failure modes before shipping"). Required adding to evaluate/implement skill.

Cat 8 (Scope Leakage):

  • Signal in PR: diff contains new files with no corresponding "Closes #N" or "Fixes #N."
  • Signal in review: scope-fidelity dimension finds "X% unrequested" scope.
  • Earliest detection: retroactive lifecycle issues filed post-merge (earliest seen: #861, #862, #863 for PR #846).

Cat 9 (Recursive Failure):

  • Signal: admin correction given in session, not reflected in any memory file, issue, or policy update.
  • Signal: same feedback given in 2+ sessions with no intervening structural change.
  • Earliest detection: issue #781 — "recurring admin corrections not persisted — feedback lost between sessions."

Admin Behavior Patterns

When the admin intervenes:

  1. "Why didn't you..." — Post-merge, when something that should have been done wasn't. Usually triggers a retroactive issue. Examples: #900 (3 acceptance criteria not filed as follow-ups), #920 (review gate clears without proof of review).

  2. "This is the wrong level" — Immediately after discovering the wrong abstraction. Usually triggers a revert. Examples: #712 (kernel-path hook), PR #720 (one-issue-per-run rule). Admin says "wrong abstraction level" or "too prescriptive."

  3. "I told you this before" — When a feedback lesson hasn't been retained. Triggers both a fix AND a meta-issue about why lessons aren't sticking. Examples: #781 (E2E testing feedback), issue #959 (verification discipline).

  4. "You should have..." — Appears in issue bodies where the admin describes what the implementor should have checked: "a simple grep would have found this," "reading the CLI --help would have shown this."

  5. Direct incident report — When a user (sometimes the admin acting as a user) files a detailed incident report with session transcript. High severity, usually reveals a complete failure in a real scenario. Examples: #756 (installation broken for non-Node projects), #757 (repo becomes non-operable after install).

What triggers admin "this plan is wrong" recognition:

  • Missing dual failure mode: the fix prevents the bad case but now the good case breaks too. Recognized within one or two uses of the new rule.
  • Symptom addressed, failure class not: the fix makes the specific incident not recur, but the next slightly different incident causes the same problem.
  • "Check before create" missing: system creates duplicates on retry/restart. Recognized immediately when duplicates appear.
  • Behavioral proof missing: the PR says "skill updated" but no evidence the skill produces different behavior. Admin added Policy 10 to enforce this.
  • Self-defeating mechanism: a guard or rule that blocks the agent from fixing what the guard is complaining about. Recognized when the agent reports it's stuck.

Admin recognition time:

  • Immediate (same session): dual failure mode blindness, self-defeating mechanisms, "this is the wrong level"
  • Within 24h: acceptance criteria not delivered, missing follow-up issues
  • Weeks later: 3054 dead test lines (only found during an exploration run)
  • Never (without tooling): 25 PRs shipped with zero review gate firing — required building the review battery to detect this

Research completed: 2026-03-26. 15 additional cases documented across 5 confirmed categories and 4 newly identified categories.

GitHub Evidence: Issue→Plan Inadequacy in the Kaizen Pipeline

Research date: 2026-03-26 Scope: Closed issues and PRs in Garsson-io/kaizen, with focus on plan quality, plan-to-reality gaps, and the issue→plan boundary.


Executive Summary

The GitHub history contains concrete, recurring evidence of plan inadequacy at the issue→plan boundary. The failure is not random — it clusters into five identifiable categories. In most cases examined, the issue body contained enough information to form a better plan. The failures arose from what agents chose to do with that information, not from information gaps in the issues themselves.


Example 1 — PR #832 / Issue #666: Plan addressed the spec, not the problem

Issue title: [L2] Skill metadata schema — declare dependencies, triggers, and compatibility in SKILL.md frontmatter

What the issue said: The skill chain is implicit — skills should declare their dependencies, triggers, and version compatibility in structured YAML frontmatter so the chain can be mechanically validated. Issue body explicitly states the motivation: "Adding a new skill requires reading workflow docs to understand where it fits."

What the plan produced: SkillMetadata TypeScript interface, a YAML frontmatter parser, dependency validation in kaizen-setup. 22 unit tests. All green. TypeScript builds clean.

What was missing: Zero of the 16 existing SKILL.md files were updated to use the new schema. The parser could parse the fields. Nothing populated them. The schema exists. Nobody populates it. The issue asked for "the skill chain to stop being implicit." It's still implicit.

This is documented in PR #846's Story Spine: "PR #832 closed issue #666 ('skill metadata schema for SKILL.md frontmatter'). The code was clean... But 0 of the 16 existing SKILL.md files were updated to use it. The schema exists. Nobody populates it. The issue asked for 'the skill chain to stop being implicit.' It's still implicit."

Category: Scope mismatch — plan addressed the technical spec (build the schema) rather than the stated goal (make the skill chain explicit). The issue body did contain the goal clearly. The plan extracted the work items from the body and missed the success criterion embedded in the same body.

What was missing from the plan: A "done" definition that traced back to the issue's stated goal. The plan validated technical correctness ("parser handles frontmatter"), never goal fulfillment ("is the skill chain no longer implicit?").


Example 2 — PR #816 / Issue #814: Plan fixed the symptom, explicitly refused to fix the root cause

Issue title: [L2] CI broken: ts-tests timing out on kaizen-reflect wrapper smoke test

What the issue said (acceptance criteria):

"If test timeout is the issue, fix the underlying slowness rather than increasing the timeout."

This was explicit. The acceptance criterion was in the issue body, in plain text, as the third bullet point.

What the plan produced: PR #816 adds HOOK_TIMING_SENTINEL_DISABLED env var check. Tests now complete in ~300ms instead of timing out. CI is green.

What was missing: The underlying slowness — runHookTimingSentinel() benchmarking all hooks via npx tsx, ~1-2s each — was not fixed. The review battery (documented in PR #846's Story Spine empirical validation table) found: "MISSING: Fix the underlying slowness rather than increasing the timeout." The issue explicitly said fix the slowness. The PR explicitly disabled the timeout check instead.

Category: Direct contradiction between plan and acceptance criteria. The plan solved for "CI green" and ignored the explicit requirement "don't increase the timeout." The issue body contained the requirement. The plan omitted it.

Evidence from PR #846: "PR #816 closed issue #814 ('CI broken: tests timing out'). The issue said 'fix the underlying slowness rather than increasing the timeout.' The PR disabled the timing sentinel in tests. The slowness remains."


Example 3 — PR #894 / Issue #891: Plan covered the happy path, missed all the wiring failure modes

Issue title: Merge stranded review-battery work and complete review loop spec

What the issue said: Detailed operational requirements for claude -p sessions: resumable, resilient, observable, streamable, budget-capped. The plan comment posted on the issue named these requirements explicitly.

What the plan produced: The implementation plan correctly identified the integration work: "Wire runFixLoop() from scripts/review-fix.ts into scripts/auto-dent-run.ts." It included a test plan with 5 unit tests and 1 E2E test.

What was missing from the actual PR: Post-hoc audit (issue #914) found 5 bugs in the merged PR:

  1. Fix loop spawned with 0 gaps when all dimensions timed out (wasting budget on nothing)
  2. Budget cap used total budget instead of remaining after implementation (could overshoot)
  3. Event emission boilerplate repeated 4x with no shared base
  4. Misleading maxRounds comment, missing follow-up issues for undelivered acceptance criteria
  5. Zero unit test coverage on the wiring logic — all 70 lines of integration code were untestable because they lived inline in main()

The test plan said "5 unit tests" — only 4 event emission tests were delivered. Issue #896 documents this: "PR #894 added ~70 lines of review-fix loop integration in auto-dent-run.ts with zero unit test coverage."

Additionally, three acceptance criteria from issue #891 were never delivered (stream-json for real-time progress, portable zippable run artifacts, test harness resume support). Issue #900 was filed after merge: "PR #894 closed issue #891 via 'Fixes' but 3 acceptance criteria were not delivered."

Category: Testability not assessed before writing code. The plan described what to build but not where to build it in a way that's testable. Adding wiring inline in main() made it untestable. Also: plan addressed a subset of the acceptance criteria without flagging the omissions.

What the issue body contained: Explicit operational requirements and acceptance criteria. The plan extracted some but not all of them. The testability gap was not in the issue — it was in the plan's omission of a testability pre-flight.


Example 4 — PR #970 / Issue #966: Orchestrator-batch design chosen without evaluating alternatives

Issue title: meta: review findings not posted to PRs — kaizen-review-pr skill missing store-review-finding step

What the issue said: Root cause correctly identified: kaizen-review-pr SKILL.md has no instruction to call store-review-finding. Issue body spelled out the compound fix: three layers (code bug + L1 instruction gap + L1 cross-reference).

What the plan produced: The implementation plan correctly diagnosed the code bug (handleStoreReviewBatch missing sentinel write) and the SKILL.md gap. The plan was technically accurate.

What was missed (from issue #979): The plan chose "orchestrator posts findings" (orchestrator calls store-review-batch after running all subagents) without evaluating the alternative: "agents post their own findings" (each dimension subagent calls store-review-finding directly before returning). The question — "who owns the handoff?" — was never surfaced in the plan.

The admin asked the question during implementation: "maybe the subagents should post the review findings?" That question took 30 seconds to ask. The answer — agent-stores is resilient to session death, orchestrator-batch loses all findings if the session dies after 3 of 5 agents complete — took 10 minutes to analyze. Issue #979 documents: "That question... would have saved 3 review rounds and a policy violation if asked before implementation started."

Category: Interface ownership not surfaced in the plan. The issue body described what was broken (storage step absent). It didn't surface who should own the storage step — because that's a structural design question, not a bug description. The plan inherited the obvious interpretation without questioning it.

Concrete cost: PR #970 went through 17 comments and multiple fix rounds. The review battery flagged the approach as a policy violation (skill-changes dimension). The fundamental design question was never formally answered in the plan.


Example 5 — Auto-dent batch / Issue #940: Intelligence layer built for humans, not agents

Issue title: [Epic] Auto-Dent Intelligence Debt: observable data doesn't steer future batches

What the issues said: Issues #690, #691 explicitly required machine-readable structured output for cross-batch analysis. PRD #691 "identifies the three failure modes exactly." It was written after the local tools shipped.

What the plans produced: PRs #654 and #651 posted "human-readable markdown tables" to GitHub batch progress issues. These satisfied "post summary to GitHub" — the literal issue text. The tables are not machine-parseable. batch-trends.ts can't read them.

What was missing: Issue #940 documents: "The PRD for what should have been built: #691 — Auto-Dent Knowledge Layer. It was written after the local tools shipped and identifies the three failure modes exactly. It is still open, never implemented."

The plans addressed "close this issue" rather than "achieve the goal the issue points toward." The goal — cross-batch learning — required structured JSON, not markdown. That requirement was in the issue tracker (PRD #691). The plans didn't read the parent PRD before designing the storage format.

Category: Plan didn't survey related issues and design docs before designing the solution. The information was available. The plan didn't retrieve it.


Example 6 — Auto-dent batch / Issue #957: Custom storage designed without reading existing tools

Issue title: [L2] Planning phase doesn't survey existing tools — agents design custom solutions for problems already solved

The concrete incident (from the issue body):

"Planning #940 (auto-dent batch intelligence), the planning agent designed Phase 1 as: post a hand-rolled fenced json block to a GitHub issue comment, then parse it back with a custom regex reader. The codebase already has cli-section-editor.ts write-attachment / read-attachment — named attachments on issues, fully tested, used by every other skill."

Pattern documented in the same issue:

  • Agents write custom telemetry formats when structured-data tools exist
  • Agents write inline gh issue comment calls when section-editor primitives exist
  • Agents design new state persistence when existing state files cover the case

Why it happened: The planning phase had no mandatory tools survey step. Before designing how to store data, the issue body says agents should read the CLAUDE.md key files table, relevant design docs, CLI entry points. "Currently: agents start designing before reading any of this. The codebase survey happens (if at all) after the design is already committed in a plan."

Category: Plan produced before codebase survey. The tools were discoverable. The plan didn't look.


Example 7 — Issue #724: Solution collapse at every layer

Issue title: Solution collapse at every layer — the system optimizes for closing issues, not understanding problems

The incident: A session produced: a wrong lint hook (#708, later reverted), a too-prescriptive prompt fix (#718, superseded), a second prompt fix (#720), and four meta-issues. Three PRs and six issues to address what should have been one note and one prompt change.

Root cause documented in the issue:

"- Filer collapsed to a solution: 'add a lint hook for /proc paths' instead of describing the failure mode

  • Implementor collapsed to the spec: built 22 tests for the lint hook faithfully, never questioned whether the lint hook was right
  • Fixer collapsed to the first constraint that solved the immediate symptom"

The implementor built what the spec said. The spec was wrong. The issue explicitly states: "every issue spec is treated as a contract (implement this), when it should be treated as a hypothesis (this might address the failure mode — validate before building)."

What the plan needed: A validation step — confirm the lint hook actually addresses the hang failure mode before building 22 tests for it.

Category: Plan accepted the spec's solution at face value without validating solution fitness. The issue body acknowledged this was a hypothesis ("add a lint hook"). The plan treated it as a contract.


Example 8 — PR #894 follow-on / Issue #901: Plan-dependent review dimensions silently dropped

Issue title: Bug: review battery silently skips plan-dependent dimensions instead of failing

What happened: auto-dent-run.ts called reviewBattery() without passing planText. The plan-fidelity and plan-coverage dimensions — the two most important for catching plan vs. delivery gaps — were silently filtered out because no plan was provided.

Why the plan missed this: The implementation plan for PR #894 included the review battery integration. It did not include "fetch the plan text from the issue comment before calling reviewBattery." Nobody stored the plan in a retrievable way (issue #902: "mechanistic plan storage"). Nobody fetched it when calling the review. The result: the review battery ran with 2 of its most critical dimensions silently absent, with no warning.

Issue #901 was filed after the fact. The original issue #891 didn't anticipate this failure mode. But the plan for PR #894 also didn't anticipate it, even though the review battery code clearly showed plan-dependent dimensions. Reading the source for the component being integrated would have surfaced this.

Category: Integration plan didn't read the component's source before wiring it in. The failure mode was discoverable from the code.


Pattern Analysis

The eight examples cluster into five failure categories:

Category 1: Plan validates technical correctness, not goal fulfillment

Examples: #1 (skill metadata), #2 (CI timeout)

The plan extracts work items from the issue body but not the success criteria. Tests pass. Code ships. The problem persists because "done" was defined as "code written" not "problem solved." Issue #521 documents this as a recurring pattern: "the agent optimizes for 'ship a PR' not 'fulfill the issue'."

Category 2: Plan accepts solution hypothesis as implementation contract

Examples: #7 (lint hook), #3 (partial — plan correct but test plan incomplete)

Issue #724 documents the pattern: "every issue spec is treated as a contract (implement this), when it should be treated as a hypothesis." Issue #949 filed the fix: "/kaizen-evaluate must require empirical proof of claimed problem before implementation." Issue #957 documents the same at the planning level: "you implement the spec, ship the PR, and only discover the root cause was different when the problem recurs."

Category 3: Design question not surfaced — only one implementation considered

Examples: #4 (who owns storage), #6 (custom vs. existing tools), #8 (plan text not fetched)

Issue #979 documents the category: "agents implement a design without noticing there was a choice." The orchestrator-batch vs. agent-stores question was not a question in the plan — it was a default. Issue #957 documents the sub-case where the design question is "use existing tool vs. build new tool" — and the plan doesn't ask it because it doesn't survey what exists.

Category 4: Testability not assessed pre-implementation

Examples: #3 (wiring code in main()), #8 (untestable integration)

Issue #896 documents: "70 lines of integration logic... untestable because it lived inline in main()." The plan described what to build, not where to build it in a way that's testable. The testability pre-flight (now in kaizen-implement SKILL.md) was added after this failure pattern recurred.

Category 5: Plan created before codebase survey

Examples: #6 (custom storage over existing tools), #5 (markdown over JSON without reading parent PRD)

Issue #957 is the canonical documentation. The planning phase starts designing before reading CLAUDE.md, design docs, or CLI help. "The codebase survey happens (if at all) after the design is already committed in a plan."


Does the Issue Body Contain Enough Information?

In 7 of the 8 examples examined, yes. Specifically:

  • Example 1 (#666): Issue body stated "the skill chain to stop being implicit." Enough to derive the success criterion. Not used.
  • Example 2 (#814): Issue body contained the acceptance criterion verbatim: "fix the underlying slowness rather than increasing the timeout." Not used.
  • Example 3 (#891): Issue body listed explicit operational requirements and acceptance criteria. Plan addressed a subset.
  • Example 4 (#966): Issue body described the bug correctly. Design question (interface ownership) was not in the body — it's a structural question that no bug report contains. Partial failure.
  • Example 5 (#940): Parent PRD #691 contained the machine-readability requirement. Not read before designing storage format.
  • Example 6 (#957): CLAUDE.md, design docs, and CLI help would have shown existing tools. Not read before designing custom format.
  • Example 7 (#724): Issue body framed the solution as a hypothesis. Implementor treated it as a contract.
  • Example 8 (#891/#901): Source code for reviewBattery() showed plan-dependent dimensions. Not read before wiring integration.

The one partial exception is Example 4: the interface ownership question ("who calls store-review-finding?") is a structural design question that no bug report naturally contains. It requires the plan phase to actively generate the question.


What Is Specifically Missing at the Issue→Plan Boundary

Based on the evidence, four things are absent at the issue→plan boundary:

1. A "done" definition derived from the issue's goal, not its work items

Plans extract tasks from issue bodies. They don't extract the success criterion — the statement of what must be true for the problem to be solved. Examples #1 and #2 show this clearly: the work was done; the goal was not achieved.

2. A codebase survey before the design is committed

In every case involving a storage or integration decision, the plan was written before reading the relevant source files, design docs, or CLI help. This is documented as the primary failure mode in issue #957. The plan's design decisions are made from first principles rather than from codebase knowledge.

3. Alternative generation: "what other designs would work, and how do they fail differently?"

Issue #979 documents this precisely: "The failure mode of the current system is not 'agents implement bad designs' — it's 'agents implement a design without noticing there was a choice.'" Plans contain one design. The alternative is never named. The tradeoff is never recorded. When the default design has a failure mode (orchestrator-batch loses state on session death; custom storage duplicates existing primitives), there's no record that an alternative existed.

4. Solution fitness validation: "does this solution address the right failure mode?"

Issue #724 and issue #949 both document this. Plans inherit the issue's proposed solution and implement it faithfully. The prior step — "does this solution actually address the stated problem?" — is absent. Issue #957 documents the same at planning level: "form a hypothesis, design an experiment, test it" is skipped in favor of "observe problem → spec solution → implement → hope it works."


Relationship to Existing Kaizen Mechanisms

The kaizen-evaluate skill (current state at research time) already contains:

  • Phase 3.5: Form hypotheses
  • Phase 3.7: Architecture and tooling fitness assessment
  • Scope Reduction Discipline gate
  • Solution evaluation (Phase 4): "Is the proposed mechanism the simplest one that addresses it?"

The kaizen-implement skill contains:

  • Surface the encoded hypothesis
  • Reuse check before writing utility code
  • Testability pre-flight

These were added in response to the failures documented above. The failures documented in this report are the precursors to these additions — the evidence that motivated them.

Issue #979 identifies the remaining gap: these checks are in the skill prompts (L1), but the skill prompts are long (350-495 lines per issue #947), and agents skip steps buried past a certain depth. The design question check exists as prose in the evaluate skill. It doesn't fire reliably because it's not a phase with a distinct pass/fail criterion — it's instructions embedded in a wall of text.

The kaizen system has recognized this at the meta-level (issue #947: "Skill prompt bloat and L1 aging — agents skip steps in long skills") but has not yet resolved it. The issue→plan boundary remains primarily L1-enforced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment