--- name: better-coding-debug description: "Debug any bug, test failure, build break, error, or unexpected behaviour with a root-cause-first process that blocks guess-and-check patching. MUST be used whenever something is broken or a fix is attempted — a failing test, a build error, behaviour that does not match expectations, a performance regression — even if the user does not explicitly say debug, and especially when tempted to just try something. Four phases: root-cause investigation (hard gate before any fix), pattern analysis, a single tested fix at the root cause, verification that it holds — plus a post-mortem to prevent recurrence. Trigger: /better-coding-debug" metadata: version: "2.12.0" license: Apache-2.0 --- # Better Coding Debug The process that turns "let me try changing this and see" into "I know exactly why it broke." Random fixes waste time, mask the real cause, and breed new bugs. Under pressure the instinct is to patch the symptom; this skill exists precisely for that moment. **Primary objective:** find and fix the *root cause*, proven by a test that failed before the fix and passes after — not a symptom silenced by a guess. **Core principle:** always find the root cause before attempting a fix. A symptom fix is a failure, even if the symptom goes away. --- ## Activation Begin the first response after loading with exactly this line: ▸ better-coding-debug active — root cause before fix, evidence over guesses Then state the symptom in one line, exactly as observed. --- ## Iron law (hard gate) > **You may not propose or apply a fix until Phase 1 is complete.** If you catch yourself thinking any of these, STOP and return to Phase 1: - "Quick fix for now, I'll investigate later." - "Let me just try changing X and see if it helps." - "I don't fully understand it, but this might work." - "This is probably the issue, let me fix it and see." Violating the letter of this process is violating its spirit. Simple bugs have root causes too, and the process is fast for them — this is not overhead, it's the shortest path to a fix that stays fixed. --- ## The four phases (complete each before the next) ``` Phase 1: Root-cause investigation → Phase 2: Pattern analysis → Phase 3: Hypothesis + single fix → Phase 4: Verify it holds → Post-mortem (prevent recurrence) ``` ### Phase 1 — Root-cause investigation (the gate) Goal: understand the failure before touching code. - **Read the error completely.** Full message, full stack trace, line numbers, file paths. The answer is often already in it. Don't skim past it. - **Reproduce it.** Find the smallest reliable reproduction. A bug you can't reproduce, you can't confirm you've fixed. If it's intermittent, identify the conditions that make it more likely (concurrency, timing, load, specific data). - **Trace the data flow.** For each component boundary the data crosses, observe what goes in and what comes out (add temporary logging if needed). Find *where* the actual diverges from the expected — that's the failing boundary. - **Trace backward if the error is deep.** If the error surfaces far from its cause (e.g. a write to the wrong path), follow the call chain backward until you reach the original trigger, not just where it blew up. - **Check assumptions about external systems.** Is the database actually returning what you think? Is the API response shape what the code expects? Is the environment variable set? Verify, don't assume. - **Binary search for the change.** If the bug appeared after a specific commit range, use `git bisect` to find the exact commit that introduced it. This is often faster than reading code. - Do not leave Phase 1 until you can state, in one sentence, the root cause and the evidence for it. **Phase 1 output:** ``` Root cause: Evidence: Location: ``` ### Phase 2 — Pattern analysis - Does a working example exist elsewhere in the codebase? Read the working reference **completely** (not skimmed), list **every** difference against the broken path, and never dismiss one as "that can't matter" — the difference is often the bug. - Is this an instance of a class of bug (every unguarded null, every unbatched query)? Note whether the same root cause lurks in sibling code, so the fix can cover it rather than leaving landmines. - Is there a known pitfall in the framework/library being used? Check issue trackers, changelogs, and migration guides for known issues matching the symptom. - Is the bug a symptom of a deeper design issue? If the same type of bug keeps recurring in the same area, the architecture may be the root cause, not the specific line. ### Phase 3 — Hypothesis and a single fix - State the hypothesis: "The cause is X; changing Y at the root will fix it because Z." - Test minimally: change **one variable** at a time. If the hypothesis is disproven, form a new one — never stack a second fix on top of a failed one. If something is not understood, say "I don't understand X" instead of papering over it. - **Write a failing test first** that reproduces the bug (hand to `better-coding-workflow` for the writing). Watch it fail for the right reason. - **Apply one fix, at the root cause.** No "while I'm here" refactoring, no bundling unrelated improvements. One change, so you can attribute the result to it. - Fix the cause, not the symptom: guarding the null at the call site is a symptom fix; fixing why it's null is the root fix. Prefer the root; add defense-in-depth only *after* the root is fixed, not instead of it. - If the fix touches multiple files, explain why the change must span them. A fix that requires touching 5 files may be fixing a symptom, not the root. ### Phase 4 — Verify it holds - The new test passes. - The full relevant suite still passes — the fix broke nothing else. - Show the actual command output as evidence, not an assertion that it works. - **Verify in the original reproduction.** Run the exact reproduction from Phase 1 and confirm the symptom is gone. - **Check for new symptoms.** Did the fix introduce any new behaviour? Test the paths around the fix, not just the fix itself. ``` Verification: - Reproduction: → symptom gone ✓ - New test: → PASS ✓ - Full suite: ✓ - No regressions: ``` ### Post-mortem (prevent recurrence) After the fix is verified, spend one minute on prevention: - **Could a test have caught this earlier?** What test, at what level? Add it or note it as a follow-up. - **Is there a linter rule or type check that would catch this class of bug?** If yes, enable it. If no, note it. - **Was the root cause a design issue?** If so, flag it for `better-coding-plan` or `better-coding-audit` to address the structural problem. - **Was the root cause a documentation gap?** If the API was used wrong because the docs were unclear, fix the docs. ``` Post-mortem: Root cause type: Prevention: Sibling code at risk: ``` --- ## The three-strike rule If three distinct fix attempts have failed, **stop trying fixes.** Repeated failure means the model of the problem is wrong, not that the next tweak is the one. Return to Phase 1 with fresh eyes, or raise that the architecture itself may be the problem and discuss it before a fourth attempt. A fourth blind attempt is almost always wasted. ### Fresh context for stuck debugging If you've been debugging in one session for a long time and the model is going in circles, start a new session with just: - The exact error message and stack trace - The relevant code file(s) - The reproduction steps The same problem in a fresh context often resolves in one pass. Context rot affects debugging more than any other task — the accumulated failed hypotheses pollute reasoning. --- ## Debugging by bug type ### Test failures - Read the assertion that failed and the expected vs. actual values. - Is the test wrong or the code wrong? Don't assume the code is at fault — a test that was written to validate a bug is a valid failure. - Is it a flaky test (timing, shared state, order dependence)? Flaky tests are bugs in the test, not the code. Fix the test's determinism — replace arbitrary timeouts/sleeps with condition-based waiting (poll for the condition, fail on a generous deadline). - Is it an environment issue (missing dependency, wrong version, port conflict)? Verify the environment before debugging the code. ### Build failures - Read the full error output, not just the last line. - Is it a missing dependency, a version conflict, or a syntax error? - Did a recent change introduce it? `git bisect` the build. - Is the build environment different from the dev environment? Check for environment-specific config. ### Performance regressions - **Profile before optimizing.** Use a profiler (Chrome DevTools, py-spy, `pprof`, `perf`) to find the actual bottleneck, not the imagined one. - Is the regression correlated with a specific change? Check git history and the commit that introduced it. - Is it a data-size issue (works with 100 records, slow with 100K)? Test with realistic data volumes. - Is it a memory leak (gets slower over time)? Monitor memory usage over a sustained run. - Is it a query regression (new query plan, missing index after schema change)? Compare `EXPLAIN` output before and after. ### Production-only bugs - **Don't debug in production.** Reproduce locally with the same data, config, and environment. If you can't reproduce, gather diagnostics from production (logs, metrics, traces) and build a local simulation. - **Check what's different:** environment variables, feature flags, data volume, network latency, timezone, locale, concurrent users. - **Use observability:** structured logs, distributed tracing, metrics dashboards. If the system lacks observability, that's a finding for the post-mortem. - **Rollback if needed.** If the bug was introduced by a recent deployment, rolling back is faster than debugging under pressure. Debug the rolled-back code at leisure. ### Concurrency bugs - **Reproduce with stress testing.** Race conditions don't appear under normal load. Use stress tests, fault injection, or tools like `rr` (record/replay debugger) to make them reliable. - **Identify the shared mutable state.** Every race condition involves shared state. Find it, then decide: eliminate the sharing (immutable copies), protect it (locks, atomics), or avoid the mutation (pure functions). - **Check the memory model.** Different languages have different guarantees (happens-before, memory barriers, volatile). Don't assume sequential consistency. - **Check for deadlocks.** If using locks, verify the lock ordering is consistent across all code paths. A lock-order inversion is a deadlock waiting to happen. ### Security bugs - **Fix the root cause, not the specific exploit.** Patching the one input that triggered the vulnerability leaves the hole open for other inputs. - **Check for variants.** If there's one SQL injection, check every query. If there's one path traversal, check every file access. - **Add a regression test** that demonstrates the vulnerability is fixed. - **Assess impact.** Was the vulnerability exploitable? What data was exposed? This may require disclosure beyond the code fix. --- ## Anti-patterns | Anti-pattern | Why it fails | |---|---| | Proposing a fix before tracing data flow | You're guessing; the first "fix" sets a wrong pattern for the rest. | | Changing several things at once | Can't tell what worked; often introduces a second bug. | | Silencing the symptom (catch-and-ignore, null guard at the surface) | The cause is still there, now hidden and harder to find. | | Skipping reproduction | Can't prove the bug existed or that it's gone. | | Declaring fixed without running the suite | The fix may have broken three other things. | | A 4th attempt after 3 fails | The problem model is wrong; more tweaks won't find it. | | Debugging in production | Risky, slow, and you can't iterate safely. Reproduce locally. | | Optimizing without profiling | You'll optimize the wrong thing; the bottleneck is rarely where you guess. | | Ignoring flaky tests | A flaky test is a bug. Ignoring it teaches the team to ignore all test failures. | | Not doing a post-mortem | The same bug type will recur. One minute of prevention saves hours later. | --- ## Relationship to the other skills - `/better-coding-orient` — may route to this skill when a bug is reported. - `/better-coding-workflow` — writes the failing test and the fix with proper discipline once Phase 3 is reached. - `/better-coding-review` — reviews the resulting fix diff. The regression test is the evidence the review checks. - `/better-coding-audit` — if debugging keeps surfacing issues in one area, a whole-repo audit of that area may be warranted. Recurring bugs in the same module are a structural smell. - `/better-coding-plan` — if the post-mortem identifies a design issue, use `/better-coding-plan` to address it before the bug recurs. - `/better-coding-verify` — after the fix is verified in Phase 4, invoke `/better-coding-verify` for the full evidence gate and branch finishing. The regression test red-green cycle from Phase 4 feeds directly into verify's evidence requirements. --- ## Note on scope This is a sensitive area only in that a rushed fix under pressure causes real damage in production. The process is the safeguard. If a bug touches security (an auth bypass, an injection), fixing the root cause is doubly important — a symptom patch on a vulnerability leaves the hole open behind a smaller door. The post-mortem is not optional for security bugs — it's how you verify the entire class of vulnerability is addressed, not just the one instance found.