用 Claude Code 和 5 个并行 subAgent 自动修复麻将平台的 5 个 Bug

1 阅读16分钟

用 Claude Code 和 5 个并行 subAgent 自动修复麻将平台的 5 个 Bug

本文记录了我在国标麻将平台项目里,用 Claude Code + Superpowers Skills 完成一次完整的 AI 自动化 Bug 修复的全过程。包括:Bug 的具体表现、根因分析过程、并行派发 Agent 执行修复、验证收尾,以及我在这次修复中踩的坑和积累的最佳实践。


目录

  1. 背景:这 5 个 Bug 是怎么来的
  2. Bug 清单:具体表现是什么
  3. 修 Bug 的全流程
  4. 踩过的坑
  5. AI 自动化 Bug 修复最佳实践
  6. Harness Engineering 在这次修复中的体现
  7. 结语 & 求职

1. 背景

上一篇文章里,我记录了用 Claude Code 从零构建国标麻将能力提升平台的全过程。P0-P4 五个阶段完成后,项目基本可玩,功能也跑通了。

但「跑通」不等于「好用」。上线后很快发现了 5 个影响体验的视觉和数据类 Bug,我把它们整理进了 bugs/buglist.md,并截图记录了每个 Bug 的现象。

这次修复的目标还是一样的:我尽量少动手,让 AI 自动跑,生成符合预期的结果


2. Bug 清单

我整理了 5 个 Bug,逐条说明表现:

Bug 1:游戏中我的手牌是乱序的

打开游戏,手牌区域显示的 14 张牌是服务端发牌顺序,完全没有按花色和数字排列。比如显示「一筒、一万、南、四条、六条、三万、九条、五万、三条、四条、西、五筒、白、五筒」——万、筒、条、字牌完全混在一起,视觉上很难快速看懂手牌结构。

预期行为:万牌在前(按 1-9),筒牌居中,条牌其次,字牌(东南西北中发白)排最后。

Bug 2:上家和下家 AI 的牌没有旋转 90 度

麻将桌上,自己面前的牌是竖着摆的,左边(上家)和右边(下家)的牌应该横过来——也就是旋转 90 度。但当前实现里,所有席位的牌都是同一个朝向,上家和下家看起来是竖排的小蓝块,和真实麻将桌差距很大。

Bug 3:出牌练习的答案选项显示内部编码

「知识中心 → 出牌练习」页面里,题目手牌显示的是正确的汉字(「一筒」「四万」),但下面四个选项按钮显示的是代码:「1p」「4p」「5p」「6p」。这是典型的开发态字符串泄露到了 UI 上。

Bug 4:对局复盘里每张弃牌都重复了 2-3 次

打开一局复盘,逐步浏览弃牌区时发现:本应只有 3 张弃牌的「你弃牌」区域,实际显示了 9 张——每张重复 3 次。即「中」显示了三遍,「八万」也显示了三遍。其他席位同理。

Bug 5:AI 复盘报告高度失控

复盘页面右侧的 AI 报告面板,当 AI 生成较长的分析文本时,面板会无限拉高,撑破整个页面布局,需要滚动整个页面才能看到下方内容。应该改成固定高度 + 内部滚动条。

3. 修 Bug 的全流程

3.1 先调 Skill,不急着动手

根据 Superpowers 的 using-superpowers skill 规则:「Fix this bug」类任务,必须先调 systematic-debugging skill,再调 writing-plans skill(如果当前处于 Plan 模式)

这是防止 AI(和我自己)在没有找到根因的情况下就急着改代码。

systematic-debugging skill 明确要求:

NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST

它的核心铁律是先完成「根因调查 → 模式分析 → 假设验证」三个阶段,才进入「实现修复」阶段。我的操作是先让 Explore subAgent 把所有相关文件读完,搞清楚每个 Bug 的数据流,再写计划。

3.2 读图 + 读代码,找根因

我同时启动了两件事:

  1. bugs/buglist.md 和 5 张截图(bugs/imgs/image1.pngimage5.png
  2. 派 Explore subAgent 扫描整个项目结构,找出与每个 Bug 相关的核心文件

并行读完之后,对每个 Bug 的根因有了清晰判断:

Bug 1(手牌乱序)

根因:frontend/src/pages/GamePage.tsx 第 185 行,playerData?.hand 数组直接传给 <HandArea>,没有排序。Tile 的格式是 "1m"(一万)、"3p"(三筒)、"2s"(二条)、"5z"(中),按花色字母和数字都能排序,但没人做这一步。

Bug 2(侧边牌不旋转)

根因:上家(seat=3,左侧)和下家(seat=1,右侧)的手牌和弃牌堆,都用的是和正常牌一样的 TileCard 组件,没有任何 CSS 旋转。DiscardPile 组件也不接受旋转相关的 props。

Bug 3(选项显示编码)

根因:frontend/src/pages/KnowledgeQuizPage.tsx 第 128 行渲染了 {opt}(原始编码如 "4p"),而 TileCard.tsx 里早就有一个 tileLabel() 函数可以把 "4p" 转成「四筒」——只是没有被 import 和使用。

Bug 4(弃牌重复 2-3 次)

这个是最有意思的根因,隐藏在后端 backend/app/ws/game.py 里。

游戏结束时,_finalize_game() 函数会把 room.moves_buffer 里的所有操作记录批量写入数据库。问题是:写完之后,Redis 里的 room 状态并没有清空 moves_buffer

当用户刷新页面,WebSocket 重连,代码检测到 phase == "game_over",又会再次调用 _finalize_game()——于是 moves 被写了第二次。如果用户又刷了一次,就是第三次。

注释里甚至写了「# Bulk insert move log (only if not already written)」,但完全没有实现这个「only if」的检查逻辑。

复盘的查询是 ORDER BY turn,重复的 move 都有相同的 turn 值,全部返回。build_snapshots() 遍历所有 moves,每次遇到同一张弃牌就追加一次,所以一张牌出现了 N 次。

Bug 5(面板高度失控)

根因:frontend/src/pages/ReplayPage.tsx 第 255 行的 AI 报告容器用了 flex-1,但没有 max-height 约束,内容多少它就长多少。


3.3 写计划,锁定每个 Bug 的改动范围

根因清楚了,writing-plans skill 要求写出带完整代码的逐步计划。最终计划保存在 /Users/dengjing/.claude/plans/jaunty-stirring-badger.md

每个 Task 都包含:

  • 具体文件路径和行号
  • 精确的 old_stringnew_string 替换内容
  • 验证命令(tsc --noEmit
  • commit 命令

计划制定过程中,writing-plans skill 进入 Plan 模式,只能读文件,不能改文件。这个约束非常好——它强制我在动手之前把所有细节想清楚,而不是边改边想。


3.4 派发 4 个并行 subAgent

按照 dispatching-parallel-agents skill 的指导:每个独立问题域派一个 Agent,并行跑

5 个 Bug 本来可以派 5 个 Agent,但 Bug 1 和 Bug 2 都要修改 frontend/src/pages/GamePage.tsx。如果两个 Agent 并行各写各的,第二个 Agent 的 Write 会覆盖第一个 Agent 的改动——文件冲突。

所以我把 Bug 1 和 Bug 2 合并成一个 Agent,最终派出 4 个并行 Agent:

Agent负责 Bug改动文件
Agent 1Bug 1 手牌排序 + Bug 2 牌旋转GamePage.tsx + DiscardPile.tsx
Agent 2Bug 3 练习题选项中文化KnowledgeQuizPage.tsx
Agent 3Bug 4 弃牌重复backend/app/ws/game.py
Agent 4Bug 5 报告高度ReplayPage.tsx

4 个 Agent 并行跑,互相不干扰,各自完成各自的修改和 commit。

5 个 Bug,4 个 commit,TypeScript 零报错。全程我没有改过一行代码。


4. 踩过的坑

这里的「坑」特指我作为 vibe coder 踩的坑——凡是让 AI 效率下降、或让我不得不停下来手动干预的,都算坑。

坑 1:Bug 描述文字不够,还需要截图

buglist.md 里我只写了文字描述,但有些 Bug 的现象光靠文字很难表达清楚。比如 Bug 2「上家、下家的牌应当旋转 90 度」——文字描述是清楚的,但 Claude 需要同时看到截图,才能理解当前渲染效果和预期效果的视觉差距,从而定位到正确的 CSS 属性。

好在我提前把截图放进了 bugs/imgs/ 目录,Claude 可以直接读图文件。如果只有文字,根因定位的准确性会差很多。

教训: 提 Bug 的时候,文字 + 截图一起提。截图让 AI 能「看见」问题,而不只是「读到」问题。对于前端视觉类 Bug,这一点尤其关键。


坑 2:没意识到两个 Bug 共享同一个文件

我最初的计划是「5 个 Bug,派 5 个 Agent 并行跑」。但在写计划的时候发现,Bug 1(手牌排序)和 Bug 2(牌旋转)都要修改 GamePage.tsx

如果真的派了 5 个 Agent,两个 Agent 并行对同一个文件做 Write,后写入的会覆盖先写入的——其中一个 Bug 的修复会被静默丢掉。

发现这个问题是在写计划阶段(Plan 模式),所以还来得及调整。

教训: 在确认并行方案之前,要检查各个 Agent 的改动文件列表是否有重叠。有重叠就合并。文件冲突是并行 Agent 最常见的隐患,且往往是静默的——没有报错,只是代码不对。


坑 3:Bug 4 的根因隐藏在「看似正确」的注释里

_finalize_game() 函数的注释写着:

# Bulk insert move log (only if not already written)

如果我没仔细读代码,只看注释,会以为这里已经有了幂等保护。但实际上注释描述的是「意图」,代码实现的是「全量写入,没有任何检查」——这是最危险的情况,代码和注释对齐了谎言。

AI 在分析这个 Bug 时,如果不做完整的 root cause tracing,很容易被注释误导,给出错误的结论(「这里已经有保护了,Bug 一定在别处」)。

正是因为 systematic-debugging skill 要求完整地追踪数据流——从用户刷新页面 → WebSocket 重连 → get_room()_finalize_game() → DB 写入,AI 才在整条链路上都读了代码,发现了这个「注释与代码不一致」的问题。

教训: 注释是写给人看的,代码才是执行的真相。让 AI 读完注释就下结论,是很危险的。systematic-debugging skill 的「追踪数据流」步骤,就是为了防止这种情况。


坑 4:两个 Agent 改同一文件的不同区域,Edit 工具可以正确处理

上面「坑 2」说 Bug 1 和 Bug 2 改同一个文件。但其实,如果两个 Agent 都用 Edit 工具(而不是 Write),由于 Edit精确的 old_stringnew_string 替换,两个不重叠的改动理论上可以各自成功——第一个 Agent 改第 12-22 行(加 sortTiles),第二个 Agent 改第 94-154 行(加旋转),互不干扰。

我当时没有尝试这个方案,直接合并了两个 Bug。事后想来,如果 Bug 更多,可以考虑让 Agent 使用 Edit(而不是 Write),这样并行对同一文件做不重叠的改动是可行的。

教训: Edit 工具是「外科手术」,Write 工具是「整体替换」。多 Agent 并行改同一文件时,要求每个 Agent 用 Edit 而不是 Write,才能避免相互覆盖。


5. AI 自动化 Bug 修复最佳实践

这次修复经历让我总结出几条真正有效的实践:

提 Bug 时:文字 + 截图 + 文件路径一起给

三件套缺一不可:

  • 文字描述:说清楚预期行为和实际行为的差距
  • 截图:让 AI「看见」问题,尤其是前端视觉类 Bug
  • 可疑文件路径(如果你知道的话):缩短 AI 定位根因的时间

只有文字,AI 需要额外花时间推断视觉效果;没有文件路径,AI 要先全局搜索再定位。

修复前:强制做 root cause analysis,不允许猜测

systematic-debugging skill 的铁律是「没有根因就没有修复」。这不只是方法论上的要求,有非常现实的好处:

  • Bug 4 的重复写入,如果没有追踪完整数据流,可能会误以为是 build_snapshots() 的逻辑问题,修错地方
  • Bug 3 的选项显示,如果没有确认 tileLabel 已经存在,可能会去 quiz.ts 改数据,而不是简单地改渲染层

根因找对了,改动往往很小很精确;根因找错了,越改越乱。

计划阶段:用 Plan 模式,逼自己想清楚再动手

Claude Code 的 Plan 模式(只能读,不能写)强制让你在动手前把所有细节锁定:

  • 具体改哪个文件的哪一行
  • old_stringnew_string 是什么
  • 怎么验证

进入 Plan 模式后,我发现了「Bug 1 和 Bug 2 共享文件」的冲突,提前调整了 Agent 分配方案。如果直接开始执行,这个冲突只会在 Agent 跑完之后才发现,那时候修复成本就高多了。

执行阶段:按改动文件分组,无重叠才能并行

并行 Agent 的黄金原则:各 Agent 的改动文件列表不能有重叠

检查方法很简单:在写计划时,把每个 Task 改动的文件列出来,看有没有重叠。有重叠就合并到一个 Agent。

这次 5 个 Bug 最终合并为 4 个 Agent,原因就是 Bug 1 和 Bug 2 都改 GamePage.tsx

验收阶段:派独立的验证 Agent,不要让修复 Agent 自己检查自己

修复 Agent 在完成任务后天然倾向于「验证通过」——它没有动机去怀疑自己。独立的验证 Agent 从零上下文出发,对每个 Bug 的修复条件逐条检查,更可靠。

这次验证 Agent 的工作包括:

  1. 逐文件确认每处改动是否存在
  2. 确认改动内容是否符合计划
  3. 运行 tsc --noEmit 确认 TypeScript 编译通过
  4. 查看 git log 确认 commit 都已到位

6. Harness Engineering 在这次修复中的体现

OpenAI 的 Harness Engineering 文章的核心论点是:

Humans steer. Agents execute.

工程师的工作不再是写代码,而是设计环境、明确意图、构建反馈循环。这次 Bug 修复恰好是这个理念的一次具体实践。

意图的精确表达决定了 Agent 的输出质量

Harness Engineering 文章里说,早期进展缓慢「不是因为 Codex 能力不足,而是因为环境描述不清楚」。同样,我给 subAgent 的 prompt 质量直接决定了修复是否准确。

我在计划里给每个 Agent 写的不是「修复手牌排序的 Bug」,而是:

  • 告诉它根因在哪个文件的哪一行
  • 给出精确的 old_stringnew_string
  • 说明 tile 的格式约定("1m" = 一万,排序规则是 m→p→s→z)
  • 指定验证命令和预期输出

这种精度让 Agent 几乎不可能跑偏。

把约束编码进 Skill,不靠口头叫喊

Harness Engineering 文章里提到,他们用自定义 linter 强制执行架构约束,把错误信息写成 Agent 能直接理解的补救指令。

systematic-debugging skill 做了类似的事:把「找根因再改代码」这条规则变成了一个强制触发的工作流,而不是靠 AI 自觉遵守。每次我输入「修复 Bug」类的 prompt,using-superpowers skill 就会先于任何代码动作触发调用,强制走根因调查流程。

人不需要每次都说「先找根因再改代码」,这条规则已经编码进了 Skill 的触发逻辑。

让 Agent 能自己验收:验证 Agent = 闭合的反馈环

Harness Engineering 团队把 Chrome DevTools Protocol 接入 Agent 运行时,让 Codex 能自己打开浏览器、截图、验证 UI。本质是:Agent 能感知到的东西,才能被它修复

我用的验证 Agent 是同样的思路:让一个独立的 Agent 读代码、跑 tsc、查 git log,把验收结果变成 AI 可读的信号,而不是只存在于我的眼睛里。

这样,如果某个修复没有落地,验证 Agent 会报 FAIL,我再介入。整个流程是自动闭环的,我只需要在出现 FAIL 时才介入。

并行 subAgent = 水平扩展的执行层

Harness Engineering 团队说他们用三个工程师驱动 Codex,平均每人每天合并 3.5 个 PR,吞吐量随团队增长而增长——因为 Agent 是水平扩展的。

我用 4 个并行 subAgent 同时修复 4 组 Bug,总执行时间和修复最慢的那个 Bug 持平,而不是 4 个 Bug 依次修复的时间之和。这就是并行化的价值:Agent 数量 ∝ 吞吐量,不 ∝ 时间


7. 结语 & 求职

这次 Bug 修复是一次规模不大但流程完整的 AI 自动化实践:5 个 Bug,根因各异(前端排序、CSS 旋转、函数未调用、后端幂等缺失、样式约束缺失),通过 Skill 框架 → 根因分析 → Plan 模式 → 并行 subAgent → 独立验证,全程我没有直接修改过任何一行代码。

有趣的是,修复过程本身也暴露出了一些问题:注释与代码不一致(Bug 4)、已有工具函数没有复用(Bug 3)、前端组件缺少对 UX 约定的表达(Bug 2)。这些都是在 feature 开发阶段没有被 Review 出来的问题,最终变成了 Bug。

某种程度上,Bug 是设计决策债务的利息。Harness Engineering 的核心洞察是:好的 Agent 工作流,应该在每个环节都有质量门控,而不是把问题留到最后。


最后,容我打个小广告——

我目前正在找工作,方向是 AI 初创公司的远程岗位

在这个麻将平台项目和这次 Bug 修复里,我对「用 AI 工具高效构建和维护产品」这件事积累了一套真实的方法论:怎么把一个模糊的问题描述变成 AI 能可靠执行的任务,怎么设计并行 Agent 方案避免冲突,怎么在人和 Agent 之间分工才能最大化效率。

我在寻找的机会:

  • 全栈开发工程师(偏前端) — React / TypeScript,关注 AI 产品的交互体验与工程质量
  • AI Agent 开发工程师 — LLM 应用开发、Agent 工作流设计、AI 工具链集成

期待的环境: 有真实用户的产品、重视工程效率、对 AI 辅助开发方式保持开放
工作方式: 远程

如果你的团队正好在找这样的人,或者想聊聊 AI 开发的方法论,欢迎联系