那个反复出现的 bug

📅 2026/7/17 21:17:18
那个反复出现的 bug
最早让我察觉出问题的不是一次崩溃。是一个模式。blockly-platform是我用 Claude Code 从头到尾做出来的第一个真东西——一个基于 Blockly、面向大学编程练习的平台全程只靠 Claude Code 的 Telegram 通道驱动。没有打开任何编辑器我本机上也没 check out 过这个仓库就只有一条聊天记录。我描述我想要什么Claude Code 在一台我从没直接看过的机器上把它建出来而我判断结果的方式就是在部署好的应用里点来点去。3 月 22 号首页打开是空的。GET /api/exercises/published返回 403。我在聊天里说了这事几条消息之后Claude Code 说修好了——这个端点没被加进 Spring Security 的permitAll()列表。我往下走去试分类筛选。一样是空的一样 403一样漏在同一个permitAll()列表里——同一个文件同一类修法只是不同的行。接着是练习详情页。同样的剧情同一天里第三次。三天后点赞按钮也不工作了——根因又一次POST /api/exercises/*/like同样从没被加进白名单。四次一个文件一个反复出现的缺口。这些都不是难搞的 bug。每一个单独拎出来都是一个称职的工程师想都不用想就能改的一行修改。等我注意到这个模式之后真正让我难受的是它正在发生的时候我没能注意到。我没有 diff 可以翻没有一个文件可以扫一眼然后想等等这一模一样的一类问题我们不是刚刚才修过两次吗我有的只是一段聊天记录和一个可以戳一戳的在线应用。从我坐的位置看过去第四次修复和第一次长得一模一样一条消息告诉我这事解决了。就是从那一刻起我开始怀疑问题不出在模型身上。而是没有任何一方——模型也好我也好——手上有任何东西可看。为什么纯聊天式 vibe coding 会失灵这个模式比AI 犯了个错更有意思的地方在于那四次修复每一次都是对的。Claude Code 读了报错找到缺失的permitAll()条目加上去然后往下走——每一次单独看诊断完全正确修法也完全正确。把那四轮里的任何一轮单独拿出来打分它都及格。失效的不是某一个决定。而是缺了任何能把这四个决定彼此串起来的东西。一个人类工程师一周之内第三次因为同一类漏洞去碰SecurityConfig.java会停下来想要不我干脆把整个文件都过一遍——不是因为他比 Claude Code 聪明而是因为他之前就在这儿栽过还记得。纯聊天、靠 Telegram 驱动的 vibe coding给不了任何一方那种连续性。Claude Code 看到的是当前这一轮的报错修的是当前这一轮的报错。我看到的是一条写着修好了的消息然后转头去处理清单上的下一件事。我们俩谁都没有把这个文件随时间变化的形状握在手里因为我们俩谁都没在看这个文件——我看的是一块手机屏幕而模型的上下文是这一轮窗口里有的那些东西不是一种这个文件有个反复出现的毛病的累积感受。这是第一层。第二层更让我担心因为在纯聊天这个模式里它压根没有一个显而易见的解法Claude Code 的一部分决定是会回到我这儿来批准的。可我从没读过这套代码的结构——我从没打开过这个仓库从没看过SecurityConfig.java从没看过练习、提交、判分这几块是怎么拼在一起的。所以当一个决定真的落到我手上时我批准它所依据的信息跟模型手上的差不多是一样的除了当前这条消息什么都没有。人在环路里只有在人手上握着某些模型没有的东西时才真的增加了一层监督。我手上没有。把这个说法的边界讲清楚这一切并不意味着纯聊天驱动就总是错的。对那种小的、用完就扔的东西它没问题——根本没有什么需要你去跟踪的。这个失效模式恰恰是在复杂度和风险往上爬的时候才冒出来——一旦一个项目有了足够多的活动部件文件随时间变化的形状才真的值得去跟踪。仓库本身佐证了这一点而且不只是靠我的记忆。blockly-platform从一个空仓库到一个合并进主线、能跑的平台用了 23 天、80 个 commit——很快而且看得出来快。里面找不到任何设计文档没有架构说明没有任何东西记录一个决定为什么这么做——只有一个RUNBOOK.md、一个TODO.md还有一个读起来像是事后才发现的一堆事清单的ERROR_LOG.md。至于CLAUDE.md——那个本该告诉 Claude Code 这个项目自己的规矩、诸如加端点的时候别忘了安全配置之类的文件——直到 4 月 2 号才存在。ERROR_LOG.md里每一条带日期的记录全部十八条包括那四次permitAll()的遗漏都落在 3 月 22 号到 3 月 28 号之间。护栏是在它们本该拦下的那些 bug 之后才到的不是之前。重做接替它的那个项目programming-learning-platform是在blockly-platform的 pull request 合并的同一天开始的。这不是我刻意安排的巧合——它就是第一个项目真正结束、而从中得到的教训开始起作用的那个点。它也不是一个另起炉灶的想法它自己的CLAUDE.md把它描述成一个面向 Blockly和Python 练习的平台是同一个工具从地基往上重建而不是从零开始的一个新东西。这一次我一口气改了两件事。第一件是可见性我换到了 IntelliJ IDEA 的远程开发模式项目 check out 在、也运行在跟之前同一类的远程主机上但这回上面挂着一个真正的编辑器——我能看到文件树能打开一个类能在一个 diff 落地之前把它读一遍。第二件是流程我打开了superpowers技能并把它作为不可商量的规则写进项目的CLAUDE.md——“每个任务Brainstorm → Plan → ImplementTDD。不许跳过 brainstorm。没有计划不许写代码。没有一个失败的测试之前不许动实现。”——而且在项目明确的红线里又把不许跳过 Superpowers单独列了一遍生怕第一次说得还不够清楚。这个差别体现在仓库的形状上不只是体现在我对当时感觉的记忆里。programming-learning-platform已经积累了 458 个 commit三个多月之后仍然活跃。它有一个真正的docs/树一份 PRD、按角色划分的用户流程、一份架构文档、按优先级拆分的功能规格——还有一个docs/superpowers/plans/目录里面装着四十四份带日期的实现计划从它头两周之内一直排到这个月初每一份都是一次 brainstorm 加 plan 步骤的记录而这些步骤都发生在对应的代码之前。blockly-platform留下的唯一书面痕迹是一份已经出过的岔子的日志而这一个留下的是一串刻意做出的、有先后顺序、在被实现之前就做好的决定的书面痕迹。它起作用了而且是在最要紧的那个意义上我不再盲批东西了因为我终于有了一个能真正去读的 diff还有一份可以拿来对着 diff 检查的计划。它也带来了我没预料到的摩擦而且我想说清楚这是我自己在一个特定时间、一套特定设置下的体验不是对产品本身下的定论。IntelliJ IDEA 的远程开发客户端哪怕真正的计算完全发生在服务器上在我本机上还是觉得沉——比我对一个本该是远程工作的一扇薄窗的东西所预期的明显更沉。而 JetBrains 的 Claude Code 插件有一些缺口恰好卡在可见性本该给你换来的那一样东西上清楚的沟通。我没法给一条消息附上图片——贴不了一张坏掉的布局截图也贴不了一张标注过的示意图——所以有些我本来五秒钟就能展示出来的东西反而要用好几段话去描述而描述里出错的空间也更大了。可见性和流程已经把真正的问题解决了。而我用来把它们包起来的那个工具还在跟我较劲。那套不再跟我较劲的设置一个一直在跑类似远程设置的朋友建议我在项目做到一半的时候把 IntelliJ 换成 VS Code——具体说是用 VS Code 的 Remote-SSH 插件直接在远程主机上管理programming-learning-platform让 Claude Code 插件跑在同一条连接上而不是用 JetBrains 那套自己的远程协议加它自己的插件。可见性和流程都没有任何变化而这恰恰是重点文件树还在diff 还是会在我批准任何东西之前落到我面前superpowers那套 brainstorm-plan-TDD 的循环原封不动地延续了下来因为它活在项目的CLAUDE.md里而不是活在恰好开着的那个编辑器里。变的是包在这两样东西外面的那个工具。客户端更轻了——它不再像 IDEA 那样在我本机上明显发沉。而且 Claude Code 插件让我可以直接给一条消息附上截图对一个围绕可视化积木编辑器和判分界面搭起来的平台来说这件事的意义比听上去要大一个坏掉的 Blockly 工具箱或者一个错位的判分面板是一张五秒钟的截图加一句两个词的问题而不是用三段话去试着把一个布局描述出来。关于为什么这个特定组合——VS Code 的 Remote-SSH 加上 Claude Code 插件——值得你放心把长时间、可以走开的工作交给它它背后的机制我之前已经写过这套流程能扛住你笔记本睡眠而算力从头到尾都不碰你的本机。这块地我就不在这儿重走一遍了。对这篇文章来说要紧的是更窄的一点这是第一套让可见性、流程、以及工具本身三者朝同一个方向使劲的设置而不是三个里有两个在跟第三个较劲。最后一公里可见性和流程都稳了之后最后一块拼图是那块真正让我能走开的知道 Claude Code什么时候需要我以及能在不用坐在桌前的情况下回答它。什么时候这一半是claude-code-notify一个我在受够了两件事之后做出来的工具要么守着一个终端当保姆要么二十分钟后才发现一个被 TDD 拖得很长的任务这一整段时间都卡在等我签字上。它会在真正要紧的那些时刻在 Telegram 上戳我——一轮任务真正完成了包括后台工作在内或者它需要我输入或者它报错挂了。怎么回答这一半是 Remote Control/remote-control它让我可以在手机上的 Claude App 里批准提示、回答问题而不用重新打开笔记本。收到戳说 Claude 需要我从兜里掏出手机回答——这就是那套工作流程而关于它可以怎样悄无声息地失灵我另外写过一篇如果底层那个远程会话活得不够久等你回答的时候它已经不在了。值得把这一层是什么、不是什么讲精确。它不是修好permitAll()那个问题的东西——修好它的是可见性以及superpowers那套先计划再写代码的纪律。notify 和 Remote Control 加上来的是一种能离开键盘、又不丢掉上面那两样中任何一样的能力我照样看得到 diffClaude Code 照样跟着计划走我只是不必坐在那儿这两件事也照样在发生。要是把同一套走开层拴到第一个项目那种纯 Telegram 的设置上它什么都修不好——你只会更快地被通知一个你根本没能力评估的决定已经做完了。顺序很重要先有可见性和流程走开的便利是搭在这个地基之上的不是拿它来顶替这个地基。再看一次那个 bug我有时会想如果第四次permitAll()的遗漏——点赞按钮那次——发生在后来那套设置下而不是第一套下会怎么样。我诚实的猜测是它照样会被写出来因为在一个新端点上忘掉一条安全规则对任何人、无论是人还是模型都是很容易犯一次的错。但它不会需要前面三次先发生才被逮住因为一个计划步骤会把加上这个端点和它的安全规则当成一个整体写出来而一个 diff 会把两半都摆出来——或者只摆出一半就在那儿在我批准之前。这个 bug 不会需要一个人去在一周的聊天消息里注意到一个模式。它只会需要一个人或者一个模型去注意到一个 diff 里的一处缺口——这是个小得多的要求而且是可见性真正让它变得可能的那种。这就是整个转变藏在所有这些工具名字底下的那个转变。纯聊天式的 vibe coding 之所以失败不是因为模型不擅长它的活儿——blockly-platform里每一次单独的permitAll()修复都是对的。它失败是因为过了某个规模之后牵涉其中的任何一方模型也好、人也好面前都没有足够的东西去注意到一个错误第二次发生更别说在第四次之前把它拦下来。可见性以及一套逼着你先规划再动手的流程并不会让 Claude Code 变得更聪明。它们做的是给正在盯着的那一方无论是模型还是人一样能真正去盯的东西。