| name | debugging |
| description | Debug Node.js, Python, and general bugs systematically. Covers node inspect, pdb, debugpy, post-mortem debugging, heap/CPU profiling, the 4-phase systematic debugging methodology, and common pitfalls. Use when a test fails, production crashes, behavior is unexpected, or performance problems need investigation. |
| version | 1.0.0 |
| author | Hermes Agent |
| license | MIT |
| platforms | ["linux","macos","windows"] |
| metadata | {"hermes":{"tags":["debugging","nodejs","python","pdb","debugpy","node-inspect","systematic","root-cause","breakpoints","dap"],"related_skills":["test-driven-development","plan"]}} |
Debugging
Debug Node.js, Python, and general bugs systematically. Three sub-areas:
- Systematic Debugging — 4-phase methodology for finding root cause before fixing
- Node.js Debugger —
node inspect REPL, CDP, heap/CPU profiling
- Python Debugger —
pdb, debugpy remote, remote-pdb, post-mortem
A. Systematic Debugging (All Languages)
Core principle: NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST.
The Four Phases
Phase 1: Root Cause Investigation
- Read error messages carefully — don't skip past them
- Reproduce consistently — exact steps, every time
- Check recent changes —
git log --oneline -10, git diff
- Gather evidence in multi-component systems — log at each component boundary
- Trace data flow — find where the bad value originates
Completion: Error reproduced, evidence gathered, root cause hypothesis formed.
Phase 2: Pattern Analysis
- Find working examples in the same codebase
- Compare against reference implementations
- Identify differences between working and broken
- Understand dependencies and assumptions
Phase 3: Hypothesis and Testing
- Form single hypothesis: "I think X is the root cause because Y"
- Test minimally — smallest possible change
- Verify before continuing
- If < 3 fixes failed: return to Phase 1
- If ≥ 3 fixes failed: question the architecture (see below)
Phase 4: Implementation
- Create failing test case first
- Implement single fix (root cause, not symptom)
- Verify fix — regression test + full suite
- If fix doesn't work: Rule of Three — after 3 failures, question architecture
Red Flags — STOP and Follow Process
- "Quick fix for now, investigate later"
- "Just try changing X and see if it works"
- Proposing solutions before tracing data flow
- "One more fix attempt" (when already tried 2+)
B. Node.js Debugger
Tools
node inspect — built-in, zero install, CLI REPL. Best for quick poking.
- CDP via
chrome-remote-interface — scriptable, automate breakpoints.
Quick Reference: node inspect REPL
node inspect path/to/script.js
node inspect -p <pid>
node --inspect-brk script.js
| Command | Action |
|---|
n / next | Step over |
s / step | Step into |
o / out | Step out |
c / cont | Continue |
sb('file.js', 42) | Set breakpoint |
bt | Backtrace |
repl | Drop into REPL in current scope |
exec expr | Evaluate expression |
.exit | Quit |
Attaching to Running Process
kill -SIGUSR1 <pid>
node inspect -p <pid>
node inspect ws://127.0.0.1:9229/<uuid>
Programmatic CDP
npm i -g chrome-remote-interface
node --inspect-brk=9229 target.js &
node /tmp/cdp-debug.js
Heap Snapshots & CPU Profiles
await client.Profiler.start();
await new Promise(r => setTimeout(r, 5000));
const { profile } = await client.Profiler.stop();
require('fs').writeFileSync('/tmp/cpu.cpuprofile', JSON.stringify(profile));
const chunks = [];
client.HeapProfiler.addHeapSnapshotChunk(({ chunk }) => chunks.push(chunk));
await client.HeapProfiler.takeHeapSnapshot({ reportProgress: false });
require('fs').writeFileSync('/tmp/heap.heapsnapshot', chunks.join(''));
Debugging Vitest/Jest Tests
node --inspect-brk ./node_modules/vitest/vitest.mjs run --no-file-parallelism src/app/foo.test.tsx
Pitfalls
- Wrong line numbers in TS — break at emitted JS line, or enable sourcemaps
--inspect vs --inspect-brk — use --inspect-brk to pause before code runs
- Port collisions — use
--inspect=0 for random port
Ctrl+C while paused — target stays paused; cont first
- Security — always bind to
127.0.0.1 (default)
C. Python Debugger
Tools
breakpoint() + pdb — simplest, add to source, run normally
python -m pdb — launch script under pdb, no source edits
debugpy — remote/headless, DAP protocol, for long-running processes
remote-pdb — terminal-friendly remote pdb via nc
pdb Quick Reference
| Command | Action |
|---|
n | Next line (step over) |
s | Step into |
r | Return from current function |
c | Continue |
l / ll | List source around current line |
w | Where (stack trace) |
p expr / pp expr | Print / pretty-print |
b file:line | Set breakpoint |
interact | Full Python REPL in current scope |
q | Quit |
Local breakpoint (simplest)
def compute(x, y):
result = some_helper(x)
breakpoint()
return result + y
Run normally. Remove breakpoint() before committing.
Launch under pdb
python -m pdb path/to/script.py arg1
(Pdb) b path/to/script.py:42
(Pdb) c
Debug pytest tests
python -m pytest tests/test_file.py::test_name --pdb -p no:xdist
-p no:xdist is required — pdb doesn't work under xdist.
Post-mortem On Any Exception
import pdb, sys
try:
run_the_thing()
except Exception:
pdb.post_mortem(sys.exc_info()[2])
Remote Debug with debugpy
import debugpy
debugpy.listen(("127.0.0.1", 5678))
debugpy.wait_for_client()
Launch with -m debugpy:
python -m debugpy --listen 127.0.0.1:5678 --wait-for-client script.py
remote-pdb (Simpler Alternative)
pip install remote-pdb
from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444)
nc 127.0.0.1 4444
Pitfalls
- pdb under pytest-xdist silently does nothing — always use
-p no:xdist
breakpoint() in CI hangs the process — never commit it
PYTHONBREAKPOINT=0 disables all breakpoint() calls — check env
- pdb under pytest-xdist silently does nothing — use
-p no:xdist or -n 0
- Attach to PID fails on hardened kernels — needs
ptrace_scope=0
scripts/run_tests.sh strips credentials — bugs depending on real config won't reproduce
- Forking/multiprocessing — pdb doesn't follow forks; debug each child separately