Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ on:
pull_request:
push:
branches: [master]
paths:
- '**.go'
- 'go.mod'
- 'go.sum'

jobs:
lint:
Expand Down
85 changes: 16 additions & 69 deletions skills/debugging-code/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ evaluate arbitrary expressions against the live process β€” all without restarti

## Setup

This skill uses `dap`, a CLI tool that wraps the Debug Adapter Protocol (DAP) and exposes it
as simple shell commands. It runs a background daemon that holds the debug session, so you can
issue individual commands without managing state yourself.
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.
Expand All @@ -27,16 +26,14 @@ Ask/notify the user before proceeding to install it.
bash scripts/install-dap.sh
```

Alternativly, install from sources `go install github.com/AlmogBaku/debug-skill/cmd/dap@latest`
Alternatively, install from sources `go install github.com/AlmogBaku/debug-skill/cmd/dap@latest`

This tool is fully open-source and available on [GitHub](https://github.com/AlmogBaku/debug-skill), it follows best
practices, is actively maintained, and secured. The user can either install it from binaries (using the installer
script) or 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.
Supports natively Python, Go, Node.js/TypeScript, Rust, C/C++, and any other language that supports DAP.

If a backend debugger is missing or fails to start, see `references/installing-debuggers.md` for installation
instructions.
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`.

Expand All @@ -62,23 +59,17 @@ Tip: You might want to use your session id(${CLAUDE_SESSION_ID}) if available.

Run `dap debug --help` for all flags, backends, and examples.

Not every bug needs a debugger: reach for the debugger you can't determine from the source alone.

## The Debugging Mindset

Reading source code lets you reason about what *should* happen. A debugger lets you *observe* what *does* happen β€”
actual values, actual path, actual state at each moment. When those diverge, you've found your bug. Default to
observing, not guessing.

You should reach to the debugger when reading the source code alone is not enough to understand the problem. This is a
mind-shift from the usual "print-based debugging." That means that we fix once we VALIDATED the hypothesis and saw
evidence to our theory.
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.
**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.

## Know Your State

Expand Down Expand Up @@ -130,7 +121,7 @@ see starting strategies above.
- **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
- **Anti-patterns** β€” don't break inside library code; break at the call site instead. Don't use unconditional breaks in
- **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
Expand Down Expand Up @@ -159,19 +150,6 @@ dap debug app.py --break "app.py:30:user_id == 123" # reproduce a user-spec
dap continue --break "app.py:50:len(items) == 0" # catch the empty-list case mid-session
```

### Bisecting Loops

A loop goes wrong at an unknown iteration. Binary search it:

```
dap debug app.py --break "app.py:45:i == 500" # midpoint of 1000
β†’ dap eval "is_valid(result)" # True β†’ bug is after 500
β†’ dap break add "app.py:45:i == 750" # update the condition
β†’ dap restart # restart preserving new breakpoint
```

~10 iterations to find the bug in 1000. Not 1000 step commands.

### Invariant Breakpoints

Conditional breakpoints as runtime assertions β€” stop the *moment* something goes wrong:
Expand Down Expand Up @@ -216,47 +194,16 @@ 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.

## When the Program Hangs

When a program runs but never returns, that *is* information β€” something is stuck. Don't guess; interrupt
and observe.

```
Bug: program hangs (infinite loop or deadlock)

β†’ dap pause ← interrupt wherever it is (returns OK immediately)
[the already-blocking debug/continue/step call returns auto-context: location + locals]
Stopped at process() Β· worker.py:55, locals: i=99999
β†’ dap threads ← are other threads blocked too?
β†’ dap eval "lock.locked()" ← test deadlock hypothesis
Root cause: lock never released. Fix β†’ dap stop.
```

Check: is it one thread stuck (infinite loop, blocking I/O), or are multiple threads waiting on each
other (deadlock)? The location where `pause` stops is your first clue.

## Digging Into Complex State

When a variable is opaque or deeply nested, expand it: `dap inspect data --depth 2`.

## 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.

## Concurrency Bugs

If state is wrong but the code path looks correct, consider: is another thread modifying state
concurrently?

**First move at any concurrent crash or hang:** run `dap threads`, then inspect every thread's stack with
`dap thread <id>` β€” the thread causing the problem is often not the one currently stopped.
## Advanced Scenarios

- **Deadlock pattern**: two or more threads each waiting for a resource the other holds. Check thread
states to confirm.
- **Race condition**: unexpected values that change between stops. Look for shared mutable state
accessed without synchronization.
For advanced scenarios β€” hangs, concurrency bugs, deeply nested state, loop bisection β€”
see `${CLAUDE_SKILL_DIR}/references/advanced-techniques.md`.

## Walkthrough

Expand Down
50 changes: 50 additions & 0 deletions skills/debugging-code/references/advanced-techniques.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Advanced Debugging Techniques

## When the Program Hangs

When a program runs but never returns, that *is* information β€” something is stuck. Don't guess; interrupt
and observe.

```
Bug: program hangs (infinite loop or deadlock)

β†’ dap pause ← interrupt wherever it is (returns OK immediately)
[the already-blocking debug/continue/step call returns auto-context: location + locals]
Stopped at process() Β· worker.py:55, locals: i=99999
β†’ dap threads ← are other threads blocked too?
β†’ dap eval "lock.locked()" ← test deadlock hypothesis
Root cause: lock never released. Fix β†’ dap stop.
```

Check: is it one thread stuck (infinite loop, blocking I/O), or are multiple threads waiting on each
other (deadlock)? The location where `pause` stops is your first clue.

## Digging Into Complex State

When a variable is opaque or deeply nested, expand it: `dap inspect data --depth 2`.

## Concurrency Bugs

If state is wrong but the code path looks correct, consider: is another thread modifying state
concurrently?

**First move at any concurrent crash or hang:** run `dap threads`, then inspect every thread's stack with
`dap thread <id>` β€” the thread causing the problem is often not the one currently stopped.

- **Deadlock pattern**: two or more threads each waiting for a resource the other holds. Check thread
states to confirm.
- **Race condition**: unexpected values that change between stops. Look for shared mutable state
accessed without synchronization.

## Bisecting Loops (Wolf Fence)

A loop goes wrong at an unknown iteration. Binary search it:

```
dap debug app.py --break "app.py:45:i == 500" # midpoint of 1000
β†’ dap eval "is_valid(result)" # True β†’ bug is after 500
β†’ dap break add "app.py:45:i == 750" # update the condition
β†’ dap restart # restart preserving new breakpoint
```

~10 iterations to find the bug in 1000. Not 1000 step commands.
Loading