Read [[principles/fix-root-causes]] before starting. Every debugging session follows that principle: trace to root cause, never paper over symptoms.
-
Reproduce. Get the exact error. Run the failing command, read the full output. If you can't reproduce, you can't verify your fix.
-
Read the error. The error message, stack trace, and line numbers are data. Read all of it before forming hypotheses. Most bugs tell you exactly where they are.
-
Suspect state before code. Before debugging code, check persistent state — especially on restart bugs or environment drift. See [[principles/fix-root-causes]].
- Noodle state: (stale items?), (orphaned?), (valid?)
- Environment: tmux sessions (), lock files, cached artifacts
- Config: validation ( reports diagnostics), skill frontmatter parse errors
- Persistent files: brain/ notes, plan files, todos — check for corruption or stale references
-
Isolate. Narrow the scope. Which file? Which function? Which line? Use binary search: comment out half, see if it still fails, repeat.
-
Find root cause. The first "fix" that comes to mind is usually a symptom fix. Ask "why?" until you hit the actual cause:
- Test fails → mock is wrong → interface changed → type doesn't match runtime shape → fix the type
- Build fails → import error → circular dependency → restructure the modules
- Runtime crash → undefined value → missing null check → data source returns null on empty → handle empty case at the source
-
Fix and verify. Fix the root cause, not the symptom. Then verify: run the test, run the build, exercise the feature path. "It compiles" is not verification.
-
Check for the pattern. If the bug existed in one place, grep for the same pattern elsewhere. Fix all instances, or make it structurally impossible.
If this bug could recur or affected your understanding, capture the lesson. Write a brain note, update a skill, or add a todo. The system should get smarter from every bug. See [[principles/encode-lessons-in-structure]].