A bug is a falsified assumption. Find the assumption, falsify it deliberately, observe the divergence, narrow until one line owns the lie. No speculation, no shotgun edits, no "fix and rerun" guessing.
When to Apply / NOT
Apply: test fails and cause unclear; production stack trace; intermittent / flaky behavior; wrong output without crash; regression after known commit window; heisenbug.
NOT apply: performance regression with correct outputs; security defect; symptom obvious from one-line read; architectural confusion.
Anti-patterns
- Shotgun debugging: editing several files hoping one fixes it.
- Print-and-rerun: adding logs without a target observation.
- Premature fix: patching symptom before isolating root cause.
- Ignoring the trace: stack frames are evidence.
- Changing two variables at once: defeats falsification.
- Deleting the failing test: capturing the bug is the asset.
Hypothesis Loop (language-neutral)
- Observe — Reproduce the failure deterministically.
- Trace — Read the failure artifact (stack, log, core dump).
- Hypothesize — One falsifiable claim. Rank hypotheses by likelihood.
- Instrument — Insert minimum probe (breakpoint, structured log, assertion).
- Run — Execute the minimal repro.
- Confirm or refute — If refuted, demote and pick next hypothesis.
- Narrow — Binary-search the suspect range. Use
git bisect for regressions.
- Confirm root cause — Inverse test: removing/altering the cause must restore correctness.
- Hand off — Forward to TDD: minimal repro becomes permanent failing test.
Stack-Trace Reading
- Top frame is innermost: the failure point.
- Cause vs context: An exception's
caused by chain encodes why; the stack encodes where.
- Async traces: virtual stacks drop frames between awaits — capture causal context.
- Symbol fidelity: Strip-mode binaries lose frame names. Build with debug info.
- Inlined / optimized frames:
<inlined> markers signal source-line-to-instruction map is approximate.
Parallel Tooling
| Family |
Live debugger |
Postmortem / record |
Remote attach |
| Systems (C/C++/Rust) |
gdb, lldb, rust-gdb, rust-lldb |
coredumpctl + gdb core, rr record/replay |
gdb -p <pid> / lldb -p <pid> |
| Python |
pdb, ipdb, pdbpp, breakpoint() |
faulthandler, py-spy dump, traceback module |
debugpy --listen |
| Go |
dlv debug, dlv test, dlv attach <pid> |
runtime/pprof, GOTRACEBACK=crash |
dlv connect <addr> |
| Java/Kotlin |
IntelliJ debugger, jdb |
hs_err logs, JFR, heap dump (jmap) |
JDWP -agentlib:jdwp=... |
| JavaScript/TypeScript |
node --inspect, Chrome DevTools |
--report-uncaught-exception reports |
--inspect=0.0.0.0:9229 |
| OCaml |
ocamldebug, Printexc.record_backtrace true |
core file + ocaml-gdb, memtrace |
ocamldebug -s <socket> |
Use procs (not ps) for PID. Use bat -P -p -n (not cat) for trace files. Use git grep -n -C 3 'pattern' (not grep) for callsites.
Constitutional Rules
- Reproduce before fixing.
- One hypothesis at a time.
- Evidence over inference.
- Capture the bug as a test (hand to TDD).
- Confirm with inverse.
- Bisect for regressions.
- No silent edits.
1---2name: debug3description: Hypothesis-driven defect isolation — stack-trace forensics, breakpoint strategy, state inspection, and root-cause confirmation via minimal repro. Use when a defect surfaces (test failure, crash, exception, wrong output, intermittent flake) and the cause is not immediately obvious from the change set.4---56A bug is a falsified assumption. Find the assumption, falsify it deliberately, observe the divergence, narrow until one line owns the lie. No speculation, no shotgun edits, no "fix and rerun" guessing.78## When to Apply / NOT910Apply: test fails and cause unclear; production stack trace; intermittent / flaky behavior; wrong output without crash; regression after known commit window; heisenbug.1112NOT apply: performance regression with correct outputs; security defect; symptom obvious from one-line read; architectural confusion.1314## Anti-patterns1516- **Shotgun debugging**: editing several files hoping one fixes it.17- **Print-and-rerun**: adding logs without a target observation.18- **Premature fix**: patching symptom before isolating root cause.19- **Ignoring the trace**: stack frames are evidence.20- **Changing two variables at once**: defeats falsification.21- **Deleting the failing test**: capturing the bug is the asset.2223## Hypothesis Loop (language-neutral)24251. **Observe** — Reproduce the failure deterministically.262. **Trace** — Read the failure artifact (stack, log, core dump).273. **Hypothesize** — One falsifiable claim. Rank hypotheses by likelihood.284. **Instrument** — Insert minimum probe (breakpoint, structured log, assertion).295. **Run** — Execute the minimal repro.306. **Confirm or refute** — If refuted, demote and pick next hypothesis.317. **Narrow** — Binary-search the suspect range. Use `git bisect` for regressions.328. **Confirm root cause** — Inverse test: removing/altering the cause must restore correctness.339. **Hand off** — Forward to TDD: minimal repro becomes permanent failing test.3435## Stack-Trace Reading3637- **Top frame is innermost**: the failure point.38- **Cause vs context**: An exception's `caused by` chain encodes *why*; the stack encodes *where*.39- **Async traces**: virtual stacks drop frames between awaits — capture causal context.40- **Symbol fidelity**: Strip-mode binaries lose frame names. Build with debug info.41- **Inlined / optimized frames**: `<inlined>` markers signal source-line-to-instruction map is approximate.4243## Parallel Tooling4445| Family | Live debugger | Postmortem / record | Remote attach |46|---|---|---|---|47| Systems (C/C++/Rust) | `gdb`, `lldb`, `rust-gdb`, `rust-lldb` | `coredumpctl` + `gdb core`, `rr record/replay` | `gdb -p <pid>` / `lldb -p <pid>` |48| Python | `pdb`, `ipdb`, `pdbpp`, `breakpoint()` | `faulthandler`, `py-spy dump`, traceback module | `debugpy --listen` |49| Go | `dlv debug`, `dlv test`, `dlv attach <pid>` | `runtime/pprof`, GOTRACEBACK=crash | `dlv connect <addr>` |50| Java/Kotlin | IntelliJ debugger, `jdb` | hs_err logs, JFR, heap dump (`jmap`) | JDWP `-agentlib:jdwp=...` |51| JavaScript/TypeScript | `node --inspect`, Chrome DevTools | `--report-uncaught-exception` reports | `--inspect=0.0.0.0:9229` |52| OCaml | `ocamldebug`, `Printexc.record_backtrace true` | core file + `ocaml-gdb`, memtrace | `ocamldebug -s <socket>` |5354Use `procs` (not `ps`) for PID. Use `bat -P -p -n` (not `cat`) for trace files. Use `git grep -n -C 3 'pattern'` (not `grep`) for callsites.5556## Constitutional Rules57581. **Reproduce before fixing**.592. **One hypothesis at a time**.603. **Evidence over inference**.614. **Capture the bug as a test** (hand to TDD).625. **Confirm with inverse**.636. **Bisect for regressions**.647. **No silent edits**.