Dev-Debugging — Systematic Root Cause Analysis
This skill is the thinking process for fixing bugs. It activates by change surface for errors and diagnoses, and enforces a structured
5-phase methodology for every technical issue — test failures, runtime errors,
build failures, performance regressions, integration bugs.
Boundary: This skill covers how to reason about bugs. For test harness,
reproduction frameworks, and verification tooling, see dev-testing. For
domain-specific context (API errors, hydration issues, query performance),
consult dev-backend or dev-frontend.
C0/C1 work (small local patches): See dev §0.0 Work Classifier + §0.1 Patch Fast-Path before reading references.
dev is canonical: dev §0.2 Rule Classes, §3 Verification Gate, and §5 Safety Rules apply to all work governed by this skill.
dev-debugging = root cause methodology (the thinking)
dev-testing = test harness for reproducing/verifying (the tooling)
dev §2 = summary pointer to this skill (the overview)
Core Principle
Check if the problem is structural before debugging code.
Complete root cause investigation before proposing any fix.
If Phase 1 is not done, keep investigating.
When to Activate
- Test failures, runtime errors, build failures, performance regressions
- Integration issues (API, database, third-party), CI pipeline failures
- Especially: when under time pressure or when "just one quick fix" seems obvious — that's when methodology matters most
The Phases
Phase 0: Is This a Bug or a Design Problem?
Before debugging code, ask: "Could this be a structural/design issue rather
than a code bug?" Patching symptoms of architectural debt creates an endless
stream of "bugs" that are really design consequences.
Decision Tree — escalate to architecture review if any apply:
| Signal |
Interpretation |
| Same class of bug recurring (3rd time fixing similar issue) |
Design problem — add a constraint at the architecture level |
| Bug spans multiple modules / crosses 2+ boundaries |
Boundary/coupling issue — see dev-architecture |
| Fix would require changing 3+ files simultaneously |
Likely structural — single-responsibility violation |
| Symptom appears far from cause (error in UI, root in DB layer) |
Tracing/observability gap — instrument boundaries first |
If structural: escalate to architecture review. Do not patch the symptom —
the patch creates the next bug.
Symptom vs Root Cause Fix:
| Symptom |
Likely Patch (wrong) |
Root Cause Fix (right) |
| Request timeout |
Increase timeout to 30s |
Add circuit breaker + fallback |
| OOM crash |
Increase container memory |
Find and fix the memory leak |
| N+1 query performance |
Add a cache layer in front |
Fix the query (eager load / join) |
| Duplicate records |
Add unique constraint + rescue |
Fix the race condition that creates duplicates |
| Flaky test |
Add retry/skip annotation |
Fix shared mutable state between tests |
If none of the above apply — proceed to Phase 1 (it's a code bug, not a
design problem).
Phase 1: Root Cause Investigation
Feedback loop gate: For UI, browser, TUI, visual, streaming, or agent-output bugs,
first create a red-capable loop that can fail before the fix: screenshot/assertion,
recorded terminal bytes, Playwright visual check, log fixture, or a manual repro script
with explicit pass/fail evidence. Do not patch from screenshots alone when a repeatable
probe can be built in reasonable time.
Complete these before attempting any fix:
Read the full error — stack trace, line numbers, error code, surrounding
context. Do not skim. The answer is often in the error message itself.
Reproduce consistently — exact steps to trigger the bug. If intermittent,
document frequency, conditions, and environment state. A bug you cannot
reproduce is a bug you cannot verify as fixed.
Check recent changes — run git log --oneline -10 and git diff. Check
new dependencies, config changes, environment variables. Bugs correlate with
recent changes most of the time.
Trace data flow — where does the bad value originate? Trace backward from
the failure point through the call stack until you find the source. Follow the
full causal chain from trigger → boundary → bad state → failure. Removing the
visible symptom is not a fix unless the defect that creates the bad state is gone.
Instrument component boundaries — for multi-layer systems (API → service →
database, CI → build → deploy), log input/output at each boundary BEFORE
proposing fixes.
Trace-first for distributed/async/agent failures (DEFAULT) — capture the
evidence trail before hypothesizing: request IDs, OpenTelemetry spans/logs,
Playwright traces/videos, and exact agent tool transcripts. For order-dependent,
native, concurrency, or intermittent failures that logs cannot explain, use
time-travel/replay debugging (Microsoft TTD on Windows, rr on Linux) — see
references/tool-guides.md.
For EACH component boundary:
- Log what data enters the component
- Log what data exits the component
- Verify environment/config propagation
Run once → analyze evidence → identify failing layer → investigate THAT layer
Work through these steps; skip only if clearly irrelevant to the problem at hand.
Phase 2: Pattern Analysis
Find working examples — similar working code in the same codebase. If it
worked before, use git bisect to find the breaking commit (see
references/tool-guides.md).
Compare systematically — list every difference between working and broken
code. No matter how small. Resist assuming "that can't matter."
Read reference docs completely — official documentation for the library,
API, or framework involved. Don't skim — read the full relevant section.
Check known issues — GitHub Issues, changelogs, migration guides. Someone
may have hit the same bug. Search with the exact error message.
When the bug depends on third-party library/API/framework behavior, current
error workarounds, upstream issues, changelogs, or migration guides, read the
active search skill and follow its source-fetch and evidence-status rules
before treating external material as proof.
Phase 3: Hypothesis and Testing
STRICT (DEBUG-RCA-EVIDENCE-01): before investigating any single root-cause
hypothesis, or making any root-cause claim, write at least three orthogonal
hypotheses (H1/H2/H3) and one falsifier for each. Collapse duplicates,
test against disconfirming evidence, and do not claim a root cause until the
competitors have been ruled out by evidence. If fewer than three are plausible,
state why.
Two words in that rule carry the weight. Orthogonal — three restatements of one
theory are one hypothesis, and listing them produces the feeling of rigor with none
of it. Falsifier — a hypothesis you cannot describe a disconfirming observation
for is not testable, so it can never be ruled out and will still be sitting there,
unexcluded, when you claim the winner.
List competing hypotheses first — write at least three plausible root-cause
hypotheses before investigating any single one. Include the evidence that would
support or reject each hypothesis. If fewer than three are plausible, state why.
State the leading hypothesis explicitly — "X is the root cause because
evidence Y shows Z." If you can't articulate it clearly, you don't understand
it yet.
Design a test to disprove — falsification is stronger than confirmation.
What would you expect to see if your hypothesis is wrong?
Test one variable — smallest possible change, one variable at a time.
Never fix multiple things at once.
If it fails → move to another listed hypothesis. Revert the failed change and
start from clean state. Stacking fixes obscures the root cause.
Keep the rejection record — preserve rejected hypotheses and the evidence
that rejected them. The final report must include them, not just the winning cause.
Admit ignorance — "I don't understand X" is a valid finding. Research
further rather than guessing. Record the open question explicitly.
Phase 4: Implementation
STRICT (DEBUG-TOGGLE-PROOF-01): enter implementation only after all three hold:
the captured value matches the hypothesis prediction, the repro repeats, and
toggling the suspected cause off and on removes then restores the bug. Write one
paragraph explaining the causal mechanism before patching.
The toggle is the load-bearing clause, and it is the one usually skipped. A value
that matches the prediction proves correlation; the bug disappearing when you disable
the cause and returning when you re-enable it is what distinguishes the cause from
something that merely co-occurs with it. If the suspected cause cannot be toggled,
say so and treat the diagnosis as unconfirmed rather than quietly upgrading
correlation to causation.
Write a failing test first — the test reproduces the bug. It should fail
before the fix. Use dev-testing for TDD patterns and test harness setup.
Make the minimal fix — address the root cause, not symptoms. One logical
change only.
Verify: the test passes, no regressions (run the full test suite:
npm test / pytest / equivalent).
Check for similar patterns — does the same bug class exist elsewhere in
the codebase? Search for it. Fix all instances, not just the one you found.
Document — final report and commit message explain root cause AND fix,
including rejected hypotheses and rejection evidence. Not "fixed bug"
but "fix: race condition in session middleware caused by missing await on
Redis write."
Red Flags — Return to Phase 1
If you catch yourself doing any of these, pause — root cause investigation
was likely skipped.
| Red Flag |
Why It Fails |
| "Quick fix for now, investigate later" |
First fix sets the pattern. Tech debt compounds. You won't investigate later. |
| "Just try changing X and see" |
Guessing guarantees rework. You'll be back here within the hour. |
| "Add multiple changes, run tests" |
Can't isolate cause if multiple variables changed. Revert, change ONE thing. |
| "It's probably X, let me fix that" |
"Probably" without evidence = Phase 1 not done. Go back and trace it. |
| "I don't fully understand but this might work" |
Seeing symptoms ≠ understanding root cause. Your "fix" hides the real bug. |
| "One more fix attempt" (after repeated failures) |
After repeated failures, pause and reassess architecture/assumptions. See escalation below. |
| "It works on my machine" |
Reproduce in the SAME environment as the failure. Local success proves nothing. |
| "Let me add a try/catch around it" |
Suppressing errors is not fixing them. Find WHY it throws. |
Repeated Failure Rule: After repeated failed fix attempts, pause entirely.
Each fix revealing a new problem in a different place is a sign of
architectural issues, not simple bugs. Discuss with the user before
attempting more fixes.
Slop Debugging Patterns
Slop debugging is spray-and-pray: guess, patch, pray, repeat.
| Instead of… |
Use… |
| Proposing fixes before investigation |
Complete Phase 1 checklist first |
| "Might be X" without evidence |
"Evidence shows X because [log/trace/diff]" |
| Multiple simultaneous changes |
One change at a time, revert between attempts |
| Skimming stack traces |
Read every line of stack trace, note line numbers |
Silent catch blocks that suppress errors |
Log with context ([module] error.message), re-throw or handle |
| Modifying failing tests to pass |
Fix the code, not the test — a failing test is evidence |
| Claiming "fixed" without running verification |
Run full test suite, show green output, verify the original symptom |
| Copy-pasting a fix without understanding |
Understand why the fix works, then adapt to your codebase |
| Suppressive try/catch (catch-and-ignore, catch-and-return-null) |
Fix at the source. Boundary catch with logging/re-throw is fine — see dev-architecture §4. |
| Guessing at types, nulls, or undefined values |
Add diagnostic logging, inspect actual runtime values |
| "It works now" after changing something unrelated |
Correlation ≠ causation — revert the change and test again |
| Letting an AI auto-repair loop (test healer, auto-fix) mask the defect |
Agentic repair aids run only AFTER root cause is understood; keep the failing repro as evidence |
Concrete Debugging Scenarios
Scenario A: API Returns 500
Root cause pattern: Missing input validation lets undefined values propagate into business logic. Instrument controller/service/repository boundaries to find where the bad value enters. Compare with a working endpoint that validates input with a schema. Fix: add schema validation at the entry point, write a test that sends invalid input and expects 400.
Worked example:
curl -i -X POST http://localhost:3000/api/orders \
-H 'content-type: application/json' \
-d '{"sku":"book-1"}'
Observed failure:
HTTP/1.1 500 Internal Server Error
TypeError: Cannot read properties of undefined (reading 'toFixed')
at calculateTotal (src/orders/service.ts:42:21)
at createOrder (src/orders/controller.ts:27:18)
Competing hypotheses before narrowing:
- Request validation allows missing
quantity.
- Controller mapping drops
quantity before service call.
- Repository returns an order row with
quantity = null.
Boundary instrumentation:
DEBUG=orders:* npm run dev
curl -s -X POST http://localhost:3000/api/orders \
-H 'content-type: application/json' \
-d '{"sku":"book-1"}' | jq .
Sample log output:
orders:controller input {"sku":"book-1"}
orders:controller mapped {"sku":"book-1"}
orders:service input {"sku":"book-1"}
orders:repository skipped insert due service error
Rejections: repository-null is rejected because the repository is never reached.
Controller-drop is rejected because controller input already lacks quantity.
Root cause: entry validation accepts a payload missing a required domain field.
Fix at the entry boundary: schema rejects missing quantity; regression test posts
the same payload and expects HTTP 400 with a stable error.code.
Scenario B: React Hydration Mismatch
Root cause pattern: Server renders a value (e.g., date, locale string) that differs from client-side rendering due to environment differences (UTC vs. local timezone). Compare with components that defer environment-dependent rendering to useEffect. Fix: move environment-dependent formatting into a client component.
Scenario C: N+1 Query Performance
Root cause pattern: List endpoint lazy-loads related records per item (1 query + N queries). Enable query logging to count queries, then compare with an endpoint that uses eager loading. Fix: add include/joinedload, write a test asserting bounded query count.
Scenario D: Flaky Test (Intermittent Failure)
Root cause pattern: Test passes in isolation but fails in suite due to shared mutable state (database rows, global variables, uncleared mocks). Compare with stable tests that use transaction rollback in beforeEach/afterEach. Fix: add proper test isolation, then search for other tests missing cleanup.
When to Escalate vs When to Keep Digging
Keep Digging When:
- You have untested hypotheses from Phase 2
- You haven't read the full error message or stack trace
- You haven't checked recent changes (
git log, git diff)
- You haven't found working comparison code yet
- The bug is in YOUR code (not a third-party library)
- You still have untested approaches to try
Escalate When:
- Repeated fix attempts failed — likely architectural; needs human judgment
- Undocumented library behavior — file an issue upstream, work around it
- Environment-specific — requires access you don't have (prod DB, cloud IAM)
- Security-sensitive — don't debug auth/crypto/payment alone; flag for human review
- Multi-team dependency — bug is in another team's service or API contract
- Stalled: if investigation stalls, reassess approach
How to Escalate Well
Don't just say "I'm stuck." Provide: symptom (exact error), reproduction
steps, evidence gathered (logs, traces, bisect results), hypotheses
tested (including rejected hypotheses and rejection evidence), remaining hypotheses (untested),
and a recommendation for next steps.
Post-Mortem Discipline
After resolving any bug that:
- Was user/customer-impacting
- Took >1 hour to diagnose
- Involved 3+ failed fix attempts (per postmortem-template.md)
- Revealed a systemic issue (same bug class exists elsewhere)
Fill out references/postmortem-template.md and include it in the PR or commit.
The goal is learning, not blame. Every postmortem must produce at least one
action item that prevents the same class of bug from recurring.
Modular References
| File |
When to Read |
What It Covers |
references/methodologies.md |
Choosing a debug approach |
Five Whys, bisection, differential diagnosis, subtraction, systematic logging |
references/async-debugging.md |
Concurrency issues |
Race conditions, deadlocks, event loop blocking, promise/callback |
references/tool-guides.md |
Quick cheatsheet |
Node inspector basics, pdb basics, Chrome DevTools, git bisect, DB EXPLAIN |
references/postmortem-template.md |
After resolving a significant incident |
Blameless postmortem template |
references/performance-debugging.md |
Latency, throughput, or memory regressions |
Profiling ladder, measurement discipline |
references/runtimes/node.md |
Node.js / tsx / Bun / Deno |
Phase 0 detection, tsx source-map trap, launch recipes, exec() patterns, silent-failure table, cleanup |
references/runtimes/js/nextjs-react.md |
Next.js 16 / React 19 |
Server-vs-client attach split, hydration mismatch workflow, React Compiler debug, RSC silent failures |
references/runtimes/js/vite-vitest.md |
Vite 8 / Vitest 4 |
Console forwarding to the agent, plugin-transform debug, visual regression, HMR and build-time silent failures |
references/runtimes/js/node-backend.md |
Express 5 / Fastify 5 / NestJS 11 |
Router debug namespaces, lifecycle hooks, schema serialization drops, DI errors, AsyncLocalStorage loss |
references/runtimes/python.md |
Python (CPython 3.9+) |
Attach methods, pdb/ipdb/pudb, pytest, asyncio gotchas, PEP 768 safe attach, py-spy/memray, silent failures |
references/runtimes/rust.md |
Rust |
Hierarchy (dbg! → RUST_LOG → backtrace → gdb/lldb → tokio-console → cargo-expand), Miri UB, silent failures |
references/runtimes/go.md |
Go |
Delve launch/attach, goroutine patterns, race detector, pprof, GODEBUG, silent failures |
references/runtimes/c-cpp.md |
C/C++ |
Sanitizers (ASan/TSan/MSan/UBSan), GDB/LLDB, Valgrind, CMake debug builds, UB silent failures |
references/runtimes/jvm.md |
Java/Kotlin (JVM) |
jcmd live diagnostics, JFR profiling, JDWP/JDB, Kotlin coroutines, GraalVM native-image |
references/runtimes/swift.md |
Swift / iOS |
LLDB, Instruments, Swift concurrency, simulator CLI, crash symbolication |
references/runtimes/ruby.md |
Ruby (3.2+) |
debug gem/rdbg, binding.irb, pry, Rails tools, nil-propagation silent failures |
references/runtimes/beam.md |
Elixir/Erlang (BEAM) |
IEx.pry, Observer, :dbg/recon, supervision hiding, mailbox/atom/binary leaks |
references/tools/playwright.md |
Browser or web-surface bugs |
codegen repro, PWDEBUG, trace viewer, console/network listeners, viewport gotchas |
Read the runtime file for the stack you are actually debugging, not all of them. Each
carries a silent-failure table for its runtime — the failures that produce no
error at all — which is the part hardest to rediscover under time pressure and the
main reason these are worth loading.
Integration with Other Skills
| Skill |
Relationship |
dev §2 |
Summary of this methodology. This skill is the full version. |
dev-testing |
Phase 4 "write failing test first" → use dev-testing for test patterns and harness. dev-testing provides the tooling; this skill provides the thinking. |
dev-backend |
Server-side debugging context: API errors, database issues, middleware chains. |
dev-frontend |
Client-side debugging context: hydration, rendering, DevTools, layout shifts. |
dev-code-reviewer |
Code review catches bugs before they ship — prevention beats debugging. |
Security-Sensitive Bugs
For security-sensitive bugs (auth bypass, data leak, injection), follow the incident response in dev-security/SKILL.md before applying a fix.
Compact Summary
When context is limited, preserve: (1) Phase 0 — is it a bug or a design problem?,
(2) Core principle — no fixes without root cause,
(3) phases 0-4 — architecture check → investigate → analyze → hypothesize → implement,
(4) Repeated Failure Rule — after repeated failures, reassess, (5) one variable at a time,
(6) evidence over intuition, (7) failing test first.
1---2name: jaw-dev-debugging3description: MUST USE for any real runtime debugging in any language — crashes, silent failures, wrong output, build/test failures, flaky tests, performance regressions, and integration bugs. A 5-phase root-cause method: architecture check → investigate → analyze → hypothesize → implement. Triggers: debug this, why is X failing, flaky test, fix the crash, root cause, error, stack trace, regression, 왜 안 돼, 디버깅, 원인 분석.4---56# Dev-Debugging — Systematic Root Cause Analysis78This skill is the **thinking process** for fixing bugs. It activates by change surface for errors and diagnoses, and enforces a structured95-phase methodology for every technical issue — test failures, runtime errors,10build failures, performance regressions, integration bugs.1112**Boundary**: This skill covers how to reason about bugs. For test harness,13reproduction frameworks, and verification tooling, see `dev-testing`. For14domain-specific context (API errors, hydration issues, query performance),15consult `dev-backend` or `dev-frontend`.1617> **C0/C1 work (small local patches):** See `dev` §0.0 Work Classifier + §0.1 Patch Fast-Path before reading references.1819> **`dev` is canonical:** `dev` §0.2 Rule Classes, §3 Verification Gate, and §5 Safety Rules apply to all work governed by this skill.2021```22dev-debugging = root cause methodology (the thinking)23dev-testing = test harness for reproducing/verifying (the tooling)24dev §2 = summary pointer to this skill (the overview)25```2627---2829## Core Principle3031Check if the problem is structural before debugging code.32Complete root cause investigation before proposing any fix.33If Phase 1 is not done, keep investigating.3435---3637## When to Activate3839- Test failures, runtime errors, build failures, performance regressions40- Integration issues (API, database, third-party), CI pipeline failures41- **Especially**: when under time pressure or when "just one quick fix" seems obvious — that's when methodology matters most4243---4445## The Phases4647### Phase 0: Is This a Bug or a Design Problem?4849Before debugging code, ask: "Could this be a structural/design issue rather50than a code bug?" Patching symptoms of architectural debt creates an endless51stream of "bugs" that are really design consequences.5253**Decision Tree — escalate to architecture review if any apply:**5455| Signal | Interpretation |56|--------|---------------|57| Same class of bug recurring (3rd time fixing similar issue) | Design problem — add a constraint at the architecture level |58| Bug spans multiple modules / crosses 2+ boundaries | Boundary/coupling issue — see `dev-architecture` |59| Fix would require changing 3+ files simultaneously | Likely structural — single-responsibility violation |60| Symptom appears far from cause (error in UI, root in DB layer) | Tracing/observability gap — instrument boundaries first |6162**If structural**: escalate to architecture review. Do not patch the symptom —63the patch creates the next bug.6465**Symptom vs Root Cause Fix:**6667| Symptom | Likely Patch (wrong) | Root Cause Fix (right) |68|---------|---------------------|----------------------|69| Request timeout | Increase timeout to 30s | Add circuit breaker + fallback |70| OOM crash | Increase container memory | Find and fix the memory leak |71| N+1 query performance | Add a cache layer in front | Fix the query (eager load / join) |72| Duplicate records | Add unique constraint + rescue | Fix the race condition that creates duplicates |73| Flaky test | Add retry/skip annotation | Fix shared mutable state between tests |7475**If none of the above apply** — proceed to Phase 1 (it's a code bug, not a76design problem).7778---7980### Phase 1: Root Cause Investigation8182**Feedback loop gate:** For UI, browser, TUI, visual, streaming, or agent-output bugs,83first create a red-capable loop that can fail before the fix: screenshot/assertion,84recorded terminal bytes, Playwright visual check, log fixture, or a manual repro script85with explicit pass/fail evidence. Do not patch from screenshots alone when a repeatable86probe can be built in reasonable time.8788**Complete these before attempting any fix:**89901. **Read the full error** — stack trace, line numbers, error code, surrounding91 context. Do not skim. The answer is often in the error message itself.92932. **Reproduce consistently** — exact steps to trigger the bug. If intermittent,94 document frequency, conditions, and environment state. A bug you cannot95 reproduce is a bug you cannot verify as fixed.96973. **Check recent changes** — run `git log --oneline -10` and `git diff`. Check98 new dependencies, config changes, environment variables. Bugs correlate with99 recent changes most of the time.1001014. **Trace data flow** — where does the bad value originate? Trace backward from102 the failure point through the call stack until you find the source. Follow the103 full causal chain from trigger → boundary → bad state → failure. Removing the104 visible symptom is not a fix unless the defect that creates the bad state is gone.1051065. **Instrument component boundaries** — for multi-layer systems (API → service →107 database, CI → build → deploy), log input/output at each boundary BEFORE108 proposing fixes.1091106. **Trace-first for distributed/async/agent failures (DEFAULT)** — capture the111 evidence trail before hypothesizing: request IDs, OpenTelemetry spans/logs,112 Playwright traces/videos, and exact agent tool transcripts. For order-dependent,113 native, concurrency, or intermittent failures that logs cannot explain, use114 time-travel/replay debugging (Microsoft TTD on Windows, rr on Linux) — see115 `references/tool-guides.md`.116117```118For EACH component boundary:119 - Log what data enters the component120 - Log what data exits the component121 - Verify environment/config propagation122Run once → analyze evidence → identify failing layer → investigate THAT layer123```124125Work through these steps; skip only if clearly irrelevant to the problem at hand.126127### Phase 2: Pattern Analysis1281291. **Find working examples** — similar working code in the same codebase. If it130 worked before, use `git bisect` to find the breaking commit (see131 `references/tool-guides.md`).1321332. **Compare systematically** — list every difference between working and broken134 code. No matter how small. Resist assuming "that can't matter."1351363. **Read reference docs completely** — official documentation for the library,137 API, or framework involved. Don't skim — read the full relevant section.1381394. **Check known issues** — GitHub Issues, changelogs, migration guides. Someone140 may have hit the same bug. Search with the exact error message.141142When the bug depends on third-party library/API/framework behavior, current143error workarounds, upstream issues, changelogs, or migration guides, read the144active `search` skill and follow its source-fetch and evidence-status rules145before treating external material as proof.146147### Phase 3: Hypothesis and Testing148149**STRICT (DEBUG-RCA-EVIDENCE-01):** before investigating any single root-cause150hypothesis, or making any root-cause claim, write at least three **orthogonal**151hypotheses (`H1`/`H2`/`H3`) and **one falsifier for each**. Collapse duplicates,152test against disconfirming evidence, and do not claim a root cause until the153competitors have been ruled out **by evidence**. If fewer than three are plausible,154state why.155156Two words in that rule carry the weight. **Orthogonal** — three restatements of one157theory are one hypothesis, and listing them produces the feeling of rigor with none158of it. **Falsifier** — a hypothesis you cannot describe a disconfirming observation159for is not testable, so it can never be ruled out and will still be sitting there,160unexcluded, when you claim the winner.1611621. **List competing hypotheses first** — write at least three plausible root-cause163 hypotheses before investigating any single one. Include the evidence that would164 support or reject each hypothesis. If fewer than three are plausible, state why.1651662. **State the leading hypothesis explicitly** — "X is the root cause because167 evidence Y shows Z." If you can't articulate it clearly, you don't understand168 it yet.1691703. **Design a test to disprove** — falsification is stronger than confirmation.171 What would you expect to see if your hypothesis is wrong?1721734. **Test one variable** — smallest possible change, one variable at a time.174 Never fix multiple things at once.1751765. **If it fails** → move to another listed hypothesis. Revert the failed change and177 start from clean state. Stacking fixes obscures the root cause.1781796. **Keep the rejection record** — preserve rejected hypotheses and the evidence180 that rejected them. The final report must include them, not just the winning cause.1811827. **Admit ignorance** — "I don't understand X" is a valid finding. Research183 further rather than guessing. Record the open question explicitly.184185### Phase 4: Implementation186187**STRICT (DEBUG-TOGGLE-PROOF-01):** enter implementation only after all three hold:188the captured value matches the hypothesis prediction, the repro repeats, and189**toggling the suspected cause off and on removes then restores the bug.** Write one190paragraph explaining the causal mechanism before patching.191192The toggle is the load-bearing clause, and it is the one usually skipped. A value193that matches the prediction proves correlation; the bug disappearing when you disable194the cause and returning when you re-enable it is what distinguishes the cause from195something that merely co-occurs with it. If the suspected cause cannot be toggled,196say so and treat the diagnosis as unconfirmed rather than quietly upgrading197correlation to causation.1981991. **Write a failing test first** — the test reproduces the bug. It should fail200 before the fix. Use `dev-testing` for TDD patterns and test harness setup.2012022. **Make the minimal fix** — address the root cause, not symptoms. One logical203 change only.2042053. **Verify**: the test passes, no regressions (run the full test suite:206 `npm test` / `pytest` / equivalent).2072084. **Check for similar patterns** — does the same bug class exist elsewhere in209 the codebase? Search for it. Fix all instances, not just the one you found.2102115. **Document** — final report and commit message explain root cause AND fix,212 including rejected hypotheses and rejection evidence. Not "fixed bug"213 but "fix: race condition in session middleware caused by missing await on214 Redis write."215216---217218## Red Flags — Return to Phase 1219220If you catch yourself doing any of these, pause — root cause investigation221was likely skipped.222223| Red Flag | Why It Fails |224|----------|-------------|225| "Quick fix for now, investigate later" | First fix sets the pattern. Tech debt compounds. You won't investigate later. |226| "Just try changing X and see" | Guessing guarantees rework. You'll be back here within the hour. |227| "Add multiple changes, run tests" | Can't isolate cause if multiple variables changed. Revert, change ONE thing. |228| "It's probably X, let me fix that" | "Probably" without evidence = Phase 1 not done. Go back and trace it. |229| "I don't fully understand but this might work" | Seeing symptoms ≠ understanding root cause. Your "fix" hides the real bug. |230| "One more fix attempt" (after repeated failures) | After repeated failures, pause and reassess architecture/assumptions. See escalation below. |231| "It works on my machine" | Reproduce in the SAME environment as the failure. Local success proves nothing. |232| "Let me add a try/catch around it" | Suppressing errors is not fixing them. Find WHY it throws. |233234**Repeated Failure Rule**: After repeated failed fix attempts, pause entirely.235Each fix revealing a new problem in a different place is a sign of236**architectural issues**, not simple bugs. Discuss with the user before237attempting more fixes.238239---240241## Slop Debugging Patterns242243Slop debugging is spray-and-pray: guess, patch, pray, repeat.244245| Instead of… | Use… |246|-------------|------|247| Proposing fixes before investigation | Complete Phase 1 checklist first |248| "Might be X" without evidence | "Evidence shows X because [log/trace/diff]" |249| Multiple simultaneous changes | One change at a time, revert between attempts |250| Skimming stack traces | Read every line of stack trace, note line numbers |251| Silent `catch` blocks that suppress errors | Log with context (`[module] error.message`), re-throw or handle |252| Modifying failing tests to pass | Fix the code, not the test — a failing test is evidence |253| Claiming "fixed" without running verification | Run full test suite, show green output, verify the original symptom |254| Copy-pasting a fix without understanding | Understand why the fix works, then adapt to your codebase |255| Suppressive try/catch (catch-and-ignore, catch-and-return-null) | Fix at the source. Boundary catch with logging/re-throw is fine — see dev-architecture §4. |256| Guessing at types, nulls, or undefined values | Add diagnostic logging, inspect actual runtime values |257| "It works now" after changing something unrelated | Correlation ≠ causation — revert the change and test again |258| Letting an AI auto-repair loop (test healer, auto-fix) mask the defect | Agentic repair aids run only AFTER root cause is understood; keep the failing repro as evidence |259260---261262## Concrete Debugging Scenarios263264### Scenario A: API Returns 500265266Root cause pattern: Missing input validation lets undefined values propagate into business logic. Instrument controller/service/repository boundaries to find where the bad value enters. Compare with a working endpoint that validates input with a schema. Fix: add schema validation at the entry point, write a test that sends invalid input and expects 400.267268Worked example:269270```bash271curl -i -X POST http://localhost:3000/api/orders \272 -H 'content-type: application/json' \273 -d '{"sku":"book-1"}'274```275276Observed failure:277278```text279HTTP/1.1 500 Internal Server Error280TypeError: Cannot read properties of undefined (reading 'toFixed')281 at calculateTotal (src/orders/service.ts:42:21)282 at createOrder (src/orders/controller.ts:27:18)283```284285Competing hypotheses before narrowing:2862871. Request validation allows missing `quantity`.2882. Controller mapping drops `quantity` before service call.2893. Repository returns an order row with `quantity = null`.290291Boundary instrumentation:292293```bash294DEBUG=orders:* npm run dev295curl -s -X POST http://localhost:3000/api/orders \296 -H 'content-type: application/json' \297 -d '{"sku":"book-1"}' | jq .298```299300Sample log output:301302```text303orders:controller input {"sku":"book-1"}304orders:controller mapped {"sku":"book-1"}305orders:service input {"sku":"book-1"}306orders:repository skipped insert due service error307```308309Rejections: repository-null is rejected because the repository is never reached.310Controller-drop is rejected because controller input already lacks `quantity`.311Root cause: entry validation accepts a payload missing a required domain field.312Fix at the entry boundary: schema rejects missing `quantity`; regression test posts313the same payload and expects HTTP 400 with a stable `error.code`.314315### Scenario B: React Hydration Mismatch316317Root cause pattern: Server renders a value (e.g., date, locale string) that differs from client-side rendering due to environment differences (UTC vs. local timezone). Compare with components that defer environment-dependent rendering to useEffect. Fix: move environment-dependent formatting into a client component.318319### Scenario C: N+1 Query Performance320321Root cause pattern: List endpoint lazy-loads related records per item (1 query + N queries). Enable query logging to count queries, then compare with an endpoint that uses eager loading. Fix: add include/joinedload, write a test asserting bounded query count.322323### Scenario D: Flaky Test (Intermittent Failure)324325Root cause pattern: Test passes in isolation but fails in suite due to shared mutable state (database rows, global variables, uncleared mocks). Compare with stable tests that use transaction rollback in beforeEach/afterEach. Fix: add proper test isolation, then search for other tests missing cleanup.326327---328329## When to Escalate vs When to Keep Digging330331### Keep Digging When:332333- You have untested hypotheses from Phase 2334- You haven't read the full error message or stack trace335- You haven't checked recent changes (`git log`, `git diff`)336- You haven't found working comparison code yet337- The bug is in YOUR code (not a third-party library)338- You still have untested approaches to try339340### Escalate When:341342- **Repeated fix attempts failed** — likely architectural; needs human judgment343- **Undocumented library behavior** — file an issue upstream, work around it344- **Environment-specific** — requires access you don't have (prod DB, cloud IAM)345- **Security-sensitive** — don't debug auth/crypto/payment alone; flag for human review346- **Multi-team dependency** — bug is in another team's service or API contract347- **Stalled**: if investigation stalls, reassess approach348349### How to Escalate Well350351Don't just say "I'm stuck." Provide: **symptom** (exact error), **reproduction352steps**, **evidence gathered** (logs, traces, bisect results), **hypotheses353tested** (including rejected hypotheses and rejection evidence), **remaining hypotheses** (untested),354and a **recommendation** for next steps.355356---357358## Post-Mortem Discipline359360After resolving any bug that:361- Was user/customer-impacting362- Took >1 hour to diagnose363- Involved 3+ failed fix attempts (per postmortem-template.md)364- Revealed a systemic issue (same bug class exists elsewhere)365366Fill out `references/postmortem-template.md` and include it in the PR or commit.367The goal is **learning, not blame**. Every postmortem must produce at least one368action item that prevents the same class of bug from recurring.369370---371372## Modular References373374| File | When to Read | What It Covers |375|------|-------------|----------------|376| `references/methodologies.md` | Choosing a debug approach | Five Whys, bisection, differential diagnosis, subtraction, systematic logging |377| `references/async-debugging.md` | Concurrency issues | Race conditions, deadlocks, event loop blocking, promise/callback |378| `references/tool-guides.md` | Quick cheatsheet | Node inspector basics, pdb basics, Chrome DevTools, git bisect, DB EXPLAIN |379| `references/postmortem-template.md` | After resolving a significant incident | Blameless postmortem template |380| `references/performance-debugging.md` | Latency, throughput, or memory regressions | Profiling ladder, measurement discipline |381| `references/runtimes/node.md` | Node.js / tsx / Bun / Deno | Phase 0 detection, tsx source-map trap, launch recipes, `exec()` patterns, silent-failure table, cleanup |382| `references/runtimes/js/nextjs-react.md` | Next.js 16 / React 19 | Server-vs-client attach split, hydration mismatch workflow, React Compiler debug, RSC silent failures |383| `references/runtimes/js/vite-vitest.md` | Vite 8 / Vitest 4 | Console forwarding to the agent, plugin-transform debug, visual regression, HMR and build-time silent failures |384| `references/runtimes/js/node-backend.md` | Express 5 / Fastify 5 / NestJS 11 | Router debug namespaces, lifecycle hooks, schema serialization drops, DI errors, AsyncLocalStorage loss |385| `references/runtimes/python.md` | Python (CPython 3.9+) | Attach methods, pdb/ipdb/pudb, pytest, asyncio gotchas, PEP 768 safe attach, py-spy/memray, silent failures |386| `references/runtimes/rust.md` | Rust | Hierarchy (`dbg!` → `RUST_LOG` → backtrace → gdb/lldb → tokio-console → cargo-expand), Miri UB, silent failures |387| `references/runtimes/go.md` | Go | Delve launch/attach, goroutine patterns, race detector, pprof, GODEBUG, silent failures |388| `references/runtimes/c-cpp.md` | C/C++ | Sanitizers (ASan/TSan/MSan/UBSan), GDB/LLDB, Valgrind, CMake debug builds, UB silent failures |389| `references/runtimes/jvm.md` | Java/Kotlin (JVM) | jcmd live diagnostics, JFR profiling, JDWP/JDB, Kotlin coroutines, GraalVM native-image |390| `references/runtimes/swift.md` | Swift / iOS | LLDB, Instruments, Swift concurrency, simulator CLI, crash symbolication |391| `references/runtimes/ruby.md` | Ruby (3.2+) | debug gem/rdbg, `binding.irb`, pry, Rails tools, nil-propagation silent failures |392| `references/runtimes/beam.md` | Elixir/Erlang (BEAM) | `IEx.pry`, Observer, `:dbg`/recon, supervision hiding, mailbox/atom/binary leaks |393| `references/tools/playwright.md` | Browser or web-surface bugs | codegen repro, PWDEBUG, trace viewer, console/network listeners, viewport gotchas |394395Read the runtime file for the stack you are actually debugging, not all of them. Each396carries a **silent-failure table** for its runtime — the failures that produce no397error at all — which is the part hardest to rediscover under time pressure and the398main reason these are worth loading.399400401---402403## Integration with Other Skills404405| Skill | Relationship |406|-------|-------------|407| `dev` §2 | Summary of this methodology. This skill is the full version. |408| `dev-testing` | Phase 4 "write failing test first" → use `dev-testing` for test patterns and harness. `dev-testing` provides the tooling; this skill provides the thinking. |409| `dev-backend` | Server-side debugging context: API errors, database issues, middleware chains. |410| `dev-frontend` | Client-side debugging context: hydration, rendering, DevTools, layout shifts. |411| `dev-code-reviewer` | Code review catches bugs before they ship — prevention beats debugging. |412413---414415## Security-Sensitive Bugs416417For security-sensitive bugs (auth bypass, data leak, injection), follow the incident response in `dev-security/SKILL.md` before applying a fix.418419---420421## Compact Summary422423When context is limited, preserve: (1) Phase 0 — is it a bug or a design problem?,424(2) Core principle — no fixes without root cause,425(3) phases 0-4 — architecture check → investigate → analyze → hypothesize → implement,426(4) Repeated Failure Rule — after repeated failures, reassess, (5) one variable at a time,427(6) evidence over intuition, (7) failing test first.