debugging-code
Interactively debug source code — set breakpoints, step through execution line by line, inspect live variable state, evaluate expressions against the running program, and navigate the call stack to trace root causes. Use when a program crashes, raises unexpected exceptions, produces wrong output, wh
By almogbaku · 509 installs
npx skills add almogbaku/debug-skill --skill debugging-code
Source repository · Upstream listing
Interactive Debugger
Use when a program crashes, produces wrong output, or you need to understand exactly
how execution reached a particular state — and running it again with more print statements
won't give you the answer fast enough.
You can pause a running program at any point, read live variable values and the call stack
at that exact moment, step forward line by line or jump to the next breakpoint, and
evaluate arbitrary expressions against the live process — all without restarting.
Setup
This skill uses dap , a CLI tool that background daemon to interact with the debugger via the DAP Protocol, maintain
the debugger state, so you can simply interact with it with multiple calls.
If dap isn't installed (check: command v dap ), install it NOW.
Ask/notify the user before proceeding to install it.
From Homebrew (macOS)
Installer script:
Install from sources:
This tool is open sourced and available on [GitHub](https://github.com/AlmogBaku/debug skill), maintained and follows
best practices.
Supports natively Python, Go, Node.js/TypeScript, Rust, C/C++, and any other language that supports DAP.
If a debugger backend is missing or fails to start, see references/installing debuggers.md
For all commands and flags: dap help or dap <cmd help .
Starting a Session
dap debug <file launches the program under the debugger. Backend is auto detected from the file extension.
Choose your starting strategy based on what you know:
Have a hypothesis — set a breakpoint where you expect the bug: dap debug script.py break script.py:42
Conditional breakpoint — only stop when a condition is met: dap debug script.py break "script.py:42:x 5" (
always quote specs with conditions)
Multi file app — breakpoints across modules: break src/api/routes.py:55 break src/models/user.py:30
No hypothesis, small program — walk from entry: dap debug script.py stop on entry (avoid for large projects —
startup code is noisy; bisect with breakpoints instead)
Exception, location unknown — dap debug script.py break on exception raised (Python) / all (Go/JS)
Remote process — dap debug attach host:port backend <name
Process already running (stuck server, live issue) — attach without restarting:
dap debug pid <PID backend <name
macOS + Go gotcha: dlv pid requires SIP disabled ( csrutil disable ).
Prefer starting the program under the debugger instead or attaching to a remote debugger!
Session isolation: session <name keeps concurrent agents from interfering.
Tip: You might want to use your session id(${CLAUDE SESSION ID}) if available.
Run dap debug help for all flags, backends, and examples.
The Debugging Mindset
Reach for a debugger when reading source alone can't validate the root cause.
A debugger lets you observe what does happen: actual values, actual path, actual state.
When that diverges from what should happen, you've found your bug.
Two strikes, rethink. If two hypotheses fail at the same location, your mental model is wrong.
Re read the code, form a completely different theory with different breakpoints.
Escalate gradually. Start with dap eval to test a quick hypothesis. Use conditional breakpoints
to filter noise. Fall back to full breakpoints + stepping only when you need interactive control.
Mimic the user journey. If you're debugging a user flow, set breakpoints along the path you expect the code to take.
If you expected compute() to be called, but it never is, then the bug is in the caller — not compute() , but whatever
was supposed to call it.
Set breakpoints instead of prints. When you feel the urge to print something, set a breakpoint instead.
Know Your State
Every dap execution command returns full context automatically: current location, source, locals, call stack, and
output. At each stop, ask:
Do the local variables have the values I expected?
Is the call stack showing the code path I expected?
Does the output so far reveal anything unexpected?
Trace causation up the stack. If a value is wrong at frame 0, check dap eval "<expr " frame 1 to see what the
caller passed. Keep going up ( frame 2 , frame 3 ) until you find the frame where the value first became wrong —
that's the origin of the bug, not the symptom.
Example output at a stop:
If the program exits before hitting your breakpoint:
→ Move breakpoints earlier, or restart with stop on entry .
Forming a Hypothesis
Before setting a breakpoint: "I believe the bug is in X because Y." A good hypothesis is falsifiable — your next
observation will confirm or disprove it. No hypothesis yet? Bisect with two breakpoints to narrow the search space, or
see starting strategies above.
Setting Breakpoints Strategically
Set where the problem begins , not where it manifests
Exception at line 80? Root cause is upstream — start earlier
Uncertain? Bisect: break f:20 break f:60 — wrong state before or after halves the search space
Where to break:
Boundaries — where data crosses a format, representation, or module boundary; state is cleanest here
State transitions — the line that assigns or mutates the corrupted value
Wrong branch — the condition whose inputs led to the bad path
Antipatterns — don't break inside library code; break at the call site instead. Don't use unconditional breaks in
tight loops — use conditions.
Managing Breakpoints Mid Session
As you learn more, add breakpoints deeper in the suspect code and remove ones that have
served their purpose — progressive narrowing without restarting:
If a breakpoint is on an invalid line or the adapter adjusts it, dap warns you in the output.
Conditional Breakpoints
Stop only when a condition is true — essential for loops, hot paths, and specific input values.
Syntax: "file:line:condition" (always quote).
Invariant Breakpoints
Conditional breakpoints as runtime assertions — stop the moment something goes wrong:
Navigating Execution
At each stop, choose how to advance based on what you suspect:
If you're stepping more than 3 times in a row, you need a breakpoint, not more steps.
Each stop shows the current file:line so you always know where you are.
Use dap eval "<expr " to probe live state without stepping:
Avoid eval expressions that call methods with side effects — they mutate program state and can corrupt your debugging
session. Stick to read only access unless you're intentionally testing a fix.
Skipping Ahead
When you need a quick look at a specific line without committing to a permanent breakpoint, use
dap continue to file:line . It's a disposable breakpoint — stops once, then vanishes. Good for
"I just want to see what x looks like at line 50" without managing breakpoint lifecycle.
Advanced Scenarios
For advanced scenarios — hangs, concurrency bugs, deeply nested state, loop bisection —
see ${CLAUDE SKILL DIR}/references/advanced techniques.md .
Walkthrough
Bug: compute() returns None
No hypothesis (exception, unknown location):
Verify Your Fix
While paused at the bug, use eval to test your proposed fix expression against the live state. If it
works in eval, it'll work in code. Then edit and dap restart to confirm end to end.
After applying a fix, re run the same scenario to verify. dap restart re runs with the same args and
breakpoints — a fast feedback loop. Don't trust that a fix works until you've observed the correct
behavior at the same breakpoint where you found the bug.
Cleanup
The dap session is usually automatically terminated when the program exits or after an idle timout.
When the app is not closed properly (e.g. you killed it while debugging), you can terminate it manually: dap stop .