Skip to Content

调试与排障

你写了一个 code-review Skill,security.md 里写得清清楚楚要检查 SQL 注入,结果真跑起来,一段明显的字符串拼接 SQL 就在眼皮底下,它愣是没提。

你的第一反应是在 security.md 里加粗、加 MUST、加 ALWAYS ALWAYS ALWAYS。

别急。先搞清楚它为什么没按预期工作。

第一步:读 transcript

transcript 是 AI 的执行日志——它看到了什么、想了什么、调用了哪些工具、输出了什么。在 Claude Code 中可以查看每次执行的详细过程。

读 transcript 时关注三件事:

  1. AI 是否加载了你的 SKILL.md? 有时候 Skill 根本没被触发,用户说的话没命中 description 里的关键词,AI 就当普通对话处理了。
  2. AI 是否读了你的规则文件? 你有 5 个 references 文件,但 AI 可能只加载了 3 个。看看 security.md 在不在其中。
  3. AI 对指令是怎么理解的? 有时候你写的”检查 SQL 注入风险”,AI 理解成”提醒开发者注意 SQL 注入”而不是”逐行扫描拼接 SQL 的代码”。

大部分问题在这一步就能定位。不看 transcript 就改 prompt,等于蒙眼调参。

问题定位决策树

Skill 没按预期工作 ├── 完全没反应 │ ├── description 缺少关键词 → 补充用户的常用表述 │ ├── 被其他 Skill 抢先匹配 → 检查 description 是否有重叠 │ └── 手动用 /skill-name 强制触发,确认 Skill 本身没问题 ├── 触发了,但指令没被遵循 │ ├── 指令太抽象 → 加具体的代码示例 │ ├── 指令被对话历史冲淡 → 精简 SKILL.md,删掉没用的内容 │ └── 上下文中有矛盾信息 → 检查 references 之间是否打架 ├── 遵循了,但结果不对 │ ├── 领域知识不足 → 在 references/ 中补充背景 │ ├── 规则太笼统 → 用正反代码示例替代文字描述 │ └── AI 对术语理解有偏差 → 加定义和上下文解释 └── 时好时坏 ├── SKILL.md 太长,尾部内容被 compaction 截断 → 重要指令前移 ├── 指令有歧义,AI 每次解读不同 → 改写为无歧义表述 └── 随机性导致 → 用 evals 多次运行,确认是概率问题还是确定性 bug

从上往下排查,先确认 Skill 被触发了,再看指令有没有被读到,最后才是调指令的措辞。顺序搞反了会浪费大量时间。

上下文窗口用量分析

Skill 的所有内容——SKILL.md 正文、references 文件、动态注入的命令输出——都要占用上下文窗口。如果你的 SKILL.md 加上 5 个 references 文件已经占了上下文的 30%,留给实际代码分析的空间就不够了。AI 被迫在有限空间里做取舍,你的某些规则自然会被”忽略”。

诊断方法很粗暴但有效:把 references 文件删掉一半,看审查效果是否反而更好。如果是,说明你的 Skill 内容超载了。

Compaction 的影响更隐蔽。当上下文快满时,Claude Code 会压缩对话历史来腾出空间。压缩时会保留最近触发的 Skill 内容,但如果 Skill 本身就很大,它的尾部也可能被截断。

这就是为什么我们反复强调:重要的指令放在 SKILL.md 的前半部分。放在末尾的”务必检查 SQL 注入”可能正好在 compaction 的裁剪线上。

实战:定位一个漏掉的 SQL 注入

场景:code-review 审查一个 Node.js 后端 PR,其中有这样一行:

const result = await db.query(`SELECT * FROM orders WHERE user_id = ${req.params.id}`);

教科书级的 SQL 注入。但 code-review 没报。

排查过程:

  1. 看 transcript,确认 security.md 被加载了——确实加载了
  2. 找到 security.md 中关于 SQL 注入的描述:
### SQL 注入 注意检查 SQL 注入风险。

一句话,没了。

问题找到了。“注意检查 SQL 注入风险”这句话对 AI 来说太抽象,它不知道要找什么具体模式。它可能扫了一眼觉得”用了 db.query 看起来是 ORM 的写法,应该没问题”。

修复——把抽象规则改成具体的代码模式匹配:

### SQL 注入 检查以下模式: - 模板字符串拼接 SQL:`` `SELECT ... ${variable}` `` - 字符串拼接 SQL:`"SELECT ... " + variable` - 未使用参数化查询的裸 SQL 调用 ❌ 错误示例: `db.query(\`SELECT * FROM orders WHERE user_id = ${userId}\`)` ✅ 正确写法: `db.query('SELECT * FROM orders WHERE user_id = ?', [userId])`

修改后重跑,这次立刻报了 Critical。

教训:AI 不是不能发现 SQL 注入,而是你没告诉它要找的具体”形状”是什么。第七章讲的”正反示例”原则,在调试阶段体现得最明显。

错误恢复模式

Skill 在运行时会依赖外部资源——脚本执行、子代理调用、文件读取、CLI 工具。这些环节都可能挂掉。与其祈祷一切顺利,不如在 SKILL.md 中预设兜底策略。

场景表现应对
脚本执行失败npx tsx 报错,输出一堆 stack trace在 SKILL.md 中加兜底指令:“如果脚本执行失败,跳过后处理步骤,直接输出审查结果”
子代理超时context: fork 的子代理长时间无响应设置合理的超时预期;SKILL.md 中说明”如果子代理未返回结果,用主对话完成剩余审查”
references 文件缺失Read 工具报 file not found在 SKILL.md 中加容错:“如果参考文件不存在,用你的通用知识替代,并在输出中标注’未加载团队规则‘“
动态注入命令失败!`gh pr diff` 返回错误信息在命令中用 2>/dev/null || echo "FALLBACK" 做兜底(v3 快照已展示此模式)

关键原则:优雅降级,而不是静默失败。 兜底策略执行后,必须在输出中告知用户哪个环节降级了、结果可能缺少什么。用户看到”未加载团队规则,以下审查基于通用标准”,至少知道要多看一眼。看到一份看似正常但实际少了一半检查项的报告,才是真正危险的。

调试速查表

症状最可能的原因快速验证
完全没反应description 不匹配用 /skill-name 手动触发
格式不对输出模板被忽略或截断检查模板位置是否在 SKILL.md 后半段
部分规则不生效规则文件没被加载在 transcript 中搜索文件名
结果时好时坏指令有歧义多跑几次 eval 对比结果差异
审查太浅上下文超载删减 references 后对比效果
子代理结果为空传参不足检查 $ARGUMENTS 是否包含必要信息

调试 Skill 和调试代码一个道理:先复现,再定位,最后修复。别跳过前两步直接改 prompt——那叫碰运气,不叫调试。

Last updated on