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)
brew
install
AlmogBaku/tap/dap
Installer script:
bash
scripts/install-dap.sh
Install from sources:
go
install
github.com/AlmogBaku/debug-skill/cmd/dap@latest
This tool is open-sourced and available on
GitHub
, 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
41: return result Locals: items=[] result=None Stack: main [script.py:10] → compute [script.py:41] Output: (none) If the program exits before hitting your breakpoint: Program terminated · Exit code: 1 → 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: dap continue --break app.py:50
add breakpoint deeper, then continue
dap continue --remove-break app.py:20
drop a breakpoint you're done with
dap break add app.py:42 app.py:60
add multiple breakpoints at once
dap break list
see what's set
dap break clear
start fresh
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). dap debug app.py --break "app.py:42:i == 100"
skip 99 iterations, stop on the one that matters
dap debug app.py --break "app.py:30:user_id == 123"
reproduce a user-specific bug
dap continue --break "app.py:50:len(items) == 0"
catch the empty-list case mid-session
Invariant Breakpoints Conditional breakpoints as runtime assertions — stop the moment something goes wrong: dap debug app.py --break "bank.py:68:balance < 0"
catch the overdraft
dap debug app.py --break "pipe.py:30:type(val) != int"
type violation
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. dap step
step over — trust this call, advance to next line
dap step in
step into — suspect what's inside this function
dap step out
step out — you're in the wrong place, return to caller
dap continue
jump to next breakpoint
dap continue --to file:line
run to line (temp breakpoint, auto-removed)
dap context
re-inspect current state without stepping
dap output
drain buffered stdout/stderr without full context
dap inspect < var
--depth N
expand nested/complex objects
dap pause
interrupt a running/hanging program
dap restart
restart with same args and breakpoints
dap threads
list all threads
dap thread < id
switch thread context
Each stop shows the current
file:line
so you always know where you are.
Use
dap eval "
test hypothesis on live state
dap eval "self.config" --frame 1
frame 1 = caller (may be a different file)
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 Hypothesis: result not assigned before return → dap debug script.py --break script.py:41 Locals: result=None, items=[] ← wrong, and input is also empty New hypothesis: caller passing empty list → dap eval "items" --frame 1 → [] ← confirmed → dap step out → caller at line 10, no guard for empty input → dap continue --break script.py:8 --remove-break script.py:41 ← narrowing: add breakpoint at data source, drop the one we're done with Stopped at main():8, items loaded from config as [] Root cause: missing guard. Fix → dap stop. No hypothesis (exception, unknown location): Exception: TypeError, location unknown → dap debug script.py --break-on-exception raised Stopped at compute():41, items=None Root cause: None passed where list expected. 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 .