重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
debugging-code by almogbaku/debug-skill
npx skills add https://github.com/almogbaku/debug-skill --skill debugging-code当程序崩溃、产生错误输出,或者你需要确切了解执行如何到达特定状态时使用——而仅仅通过添加更多打印语句重新运行无法快速给出答案。
你可以在任意点暂停正在运行的程序,读取该确切时刻的实时变量值和调用栈,逐行前进或跳转到下一个断点,并针对实时进程评估任意表达式——所有这些都无需重启。
此技能使用 dap,这是一个封装了调试适配器协议(DAP)并将其作为简单 shell 命令暴露的 CLI 工具。它运行一个持有调试会话的后台守护进程,因此你可以发出单个命令而无需自行管理状态。
如果 dap 未安装,请立即安装:
bash <(curl -fsSL https://raw.githubusercontent.com/AlmogBaku/debug-skill/master/install.sh)
在使用此技能之前,你必须安装 dap!在继续安装之前询问/通知用户。该工具是完全开源的,可在 GitHub 上获取,它遵循最佳实践,积极维护且安全。用户可以从二进制文件(使用安装脚本)或源代码安装。
支持:Python · Go · Node.js/TypeScript · Rust · C/C++ 如果缺少后端或后端启动失败,请参阅 ${CLAUDE_SKILL_DIR}/references/installing-debuggers.md 获取安装说明。
所有命令和标志:dap --help 或 。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
dap <cmd> --helpdap debug <文件> 在调试器下启动程序。后端根据文件扩展名自动检测。
根据你所知的情况选择启动策略:
dap debug script.py --break script.py:42dap debug script.py --break "script.py:42:x > 5"(包含条件的规范始终用引号括起来)--break src/api/routes.py:55 --break src/models/user.py:30dap debug script.py --stop-on-entry(对于大型项目避免使用——启动代码很嘈杂;改用断点进行二分查找)dap debug script.py --break-on-exception raised(Python)/ all(Go/JS)dap debug --attach host:port --backend <名称>dap debug --pid <PID> --backend <名称>会话隔离: --session <名称> 防止并发代理相互干扰。提示:如果可用,你可能希望使用你的会话 ID(${CLAUDE_SESSION_ID})。
运行 dap debug --help 查看所有标志、后端和示例。
并非每个错误都需要调试器:当你无法仅从源代码确定问题时,请使用调试器。
阅读源代码让你能够推理 应该 发生什么。调试器让你能够 观察 实际 发生 了什么——实际值、实际路径、每个时刻的实际状态。当这两者出现分歧时,你就找到了错误。默认进行观察,而非猜测。
当仅阅读源代码不足以理解问题时,你应该求助于调试器。这是从通常的“基于打印的调试”思维方式的转变。这意味着一旦我们验证了假设并看到了支持我们理论的证据,我们就会进行修复。
两次失败,重新思考。 如果在同一位置两个假设都失败了,那么你的心智模型是错误的。重新阅读代码,形成一个 完全不同的 理论,并设置不同的断点。
逐步升级。 从 dap eval 开始测试一个快速假设。使用条件断点来过滤噪音。仅当你需要交互式控制时,才退回到完整断点 + 单步执行。
每个 dap 执行命令都会自动返回完整上下文:当前位置、源代码、局部变量、调用栈和输出。在每次停止时,询问:
沿调用栈向上追溯因果关系。 如果在帧 0 处的值是错误的,检查 dap eval "<表达式>" --frame 1 以查看调用者传递了什么。继续向上(--frame 2,--frame 3),直到找到值首次出错的帧——那就是错误的根源,而不是症状。
停止时的示例输出:
Stopped at compute() · script.py:41
39: def compute(items):
40: result = None
> 41: return result
Locals: items=[] result=None
Stack: main [script.py:10] → compute [script.py:41]
Output: (none)
如果程序在命中你的断点之前退出:
Program terminated · Exit code: 1
→ 将断点设置得更早,或者使用 --stop-on-entry 重新启动。
在设置断点之前:“我相信错误在 X 处,因为 Y。”一个好的假设是可证伪的——你的下一次观察将证实或反驳它。还没有假设?使用两个断点进行二分查找以缩小搜索范围,或者查看上面的启动策略。
--break f:20 --break f:60 —— 错误状态出现在前半部分还是后半部分,可以减半搜索空间在哪里设置断点:
随着你了解更多,在可疑代码的更深层添加断点,并移除已完成任务的断点——无需重启即可逐步缩小范围:
dap continue --break app.py:50 # 在更深层添加断点,然后继续
dap continue --remove-break app.py:20 # 移除已完成的断点
dap break add app.py:42 app.py:60 # 一次性添加多个断点
dap break list # 查看已设置的断点
dap break clear # 重新开始
如果断点位于无效行或适配器调整了它,dap 会在输出中警告你。
仅当条件为真时停止——对于循环、热路径和特定输入值至关重要。语法:"文件:行:条件"(始终用引号)。
dap debug app.py --break "app.py:42:i == 100" # 跳过 99 次迭代,停在关键的那一次
dap debug app.py --break "app.py:30:user_id == 123" # 重现特定用户的错误
dap continue --break "app.py:50:len(items) == 0" # 在会话中途捕获空列表的情况
循环在未知迭代次数时出错。使用二分查找:
dap debug app.py --break "app.py:45:i == 500" # 1000 次的中点
→ dap eval "is_valid(result)" # True → 错误在 500 次之后
→ dap break add "app.py:45:i == 750" # 更新条件
→ dap restart # 重新启动并保留新断点
大约 10 次迭代即可在 1000 次中找到错误。而不是 1000 次单步命令。
将条件断点作为运行时断言——在 某个时刻 出错时立即停止:
dap debug app.py --break "bank.py:68:balance < 0" # 捕获透支
dap debug app.py --break "pipe.py:30:type(val) != int" # 类型违规
在每次停止时,根据你的怀疑选择如何前进:
如果你连续单步执行超过 3 次,你需要的是断点,而不是更多的单步。
dap step # 单步跳过 —— 信任此调用,前进到下一行
dap step in # 单步进入 —— 怀疑此函数内部
dap step out # 单步跳出 —— 你走错了地方,返回到调用者
dap continue # 跳转到下一个断点
dap continue --to file:line # 运行到指定行(临时断点,自动移除)
dap context # 重新检查当前状态而无需单步
dap output # 清空缓冲的 stdout/stderr 而无需完整上下文
dap inspect <变量> --depth N # 展开嵌套/复杂对象
dap pause # 中断正在运行/挂起的程序
dap restart # 使用相同参数和断点重新启动
dap threads # 列出所有线程
dap thread <id> # 切换线程上下文
每次停止都会显示当前的 文件:行,因此你始终知道自己的位置。
使用 dap eval "<表达式>" 在不单步的情况下探测实时状态:
dap eval "len(items)"
dap eval "user.profile.settings"
dap eval "expected == actual" # 在实时状态下测试假设
dap eval "self.config" --frame 1 # 帧 1 = 调用者(可能是一个不同的文件)
避免调用具有副作用的方法的 eval 表达式——它们会改变程序状态并可能破坏你的调试会话。除非你故意测试修复,否则坚持使用只读访问。
当程序运行但永不返回时,这 就是 信息——某个地方卡住了。不要猜测;中断并观察。
Bug: program hangs (infinite loop or deadlock)
→ dap pause ← 中断它所在的位置(立即返回 OK)
[已阻塞的调试/继续/单步调用自动返回上下文:位置 + 局部变量]
Stopped at process() · worker.py:55, locals: i=99999
→ dap threads ← 其他线程是否也被阻塞?
→ dap eval "lock.locked()" ← 测试死锁假设
Root cause: lock never released. Fix → dap stop.
检查:是一个线程卡住(无限循环、阻塞 I/O),还是多个线程相互等待(死锁)?pause 停止的位置是你的第一个线索。
当变量不透明或深度嵌套时,展开它:dap inspect data --depth 2。
当你需要快速查看特定行而不想设置永久断点时,使用 dap continue --to file:line。这是一个一次性断点——停止一次后消失。适用于“我只想看看第 50 行的 x 是什么样子”的情况,而无需管理断点的生命周期。
如果状态错误但代码路径看起来正确,考虑:是否有另一个线程在并发修改状态?
处理任何并发崩溃或挂起的首要步骤: 运行 dap threads,然后使用 dap thread <id> 检查每个线程的栈——导致问题的线程通常不是当前停止的那个。
错误:compute() 返回 None
Hypothesis: result not assigned before return
→ dap debug script.py --break script.py:41
Locals: result=None, items=[] ← 错误,且输入也为空
New hypothesis: caller passing empty list
→ dap eval "items" --frame 1 → [] ← 确认
→ 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.
无假设(异常,位置未知):
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.
在错误处暂停时,使用 eval 针对实时状态测试你提议的修复表达式。如果它在 eval 中有效,那么在代码中也会有效。然后编辑并使用 dap restart 进行端到端确认。
应用修复后,重新运行相同场景进行验证。dap restart 使用相同的参数和断点重新运行——这是一个快速的反馈循环。在你观察到在发现错误的同一断点处行为正确之前,不要相信修复有效。
当程序退出或空闲超时后,dap 会话通常会自动终止。当应用程序未正确关闭时(例如,你在调试时杀死了它),你可以手动终止它:dap stop。
每周安装次数
19
仓库
GitHub Stars
123
首次出现
5 天前
安全审计
安装于
opencode18
gemini-cli18
github-copilot18
codex18
amp18
cline18
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.
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.
If dap isn't installed, install it NOW:
bash <(curl -fsSL https://raw.githubusercontent.com/AlmogBaku/debug-skill/master/install.sh)
You MUST INSTALL dap before using this skill! Ask/notify the user before proceeding to install it. This tool is fully open-source and available on GitHub, it follows best practices, is actively maintained, and secured. The user can either install it from binaries (using the installer script) or from sources.
Supports: Python · Go · Node.js/TypeScript · Rust · C/C++ If a backend is missing or fails to start, see ${CLAUDE_SKILL_DIR}/references/installing-debuggers.md for installation instructions.
For all commands and flags: dap --help or dap <cmd> --help.
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:
dap debug script.py --break script.py:42dap debug script.py --break "script.py:42:x > 5" ( always quote specs with conditions)--break src/api/routes.py:55 --break src/models/user.py:30dap debug script.py --stop-on-entry (avoid for large projects — startup code is noisy; bisect with breakpoints instead)dap debug script.py --break-on-exception raised (Python) / all (Go/JS)dap debug --attach host:port --backend <name>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.
Not every bug needs a debugger: reach for the debugger you can't determine from the source alone.
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.
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.
Every dap execution command returns full context automatically: current location, source, locals, call stack, and output. At each stop, ask:
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:
Stopped at compute() · script.py:41
39: def compute(items):
40: result = None
> 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.
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.
--break f:20 --break f:60 — wrong state before or after halves the search spaceWhere to break:
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.
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
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.
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
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 "<expr>" to probe live state without stepping:
dap eval "len(items)"
dap eval "user.profile.settings"
dap eval "expected == actual" # 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.
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.
When a variable is opaque or deeply nested, expand it: dap inspect data --depth 2.
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.
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.
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.
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.
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.
Weekly Installs
19
Repository
GitHub Stars
123
First Seen
5 days ago
Security Audits
Gen Agent Trust HubFailSocketWarnSnykFail
Installed on
opencode18
gemini-cli18
github-copilot18
codex18
amp18
cline18
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
125,600 周安装
dap debug --pid <PID> --backend <name>