第二课:从玩具到生产

March 2, 2026 · View on GitHub

第一课让你跑起了一个最小示例。这一课讲的是:为什么那个"能跑"的代码,离"能用"还差十万八千里。

阅读时间:20-25 分钟 难度:进阶 前置知识:完成第一课课后作业,了解 CLI 子进程调用

证据标注说明(延续第一课): [事实] 有 commit / 文档 / 代码佐证 · [推断] 作者基于经验的解读 · [外部] 来自外部文档或第三方


故事背景

时间:2026 年 2 月 7 日

CLI 迁移完成后,三只猫已经能在同一个聊天室里说话了。但铲屎官问了一个问题:

"猫猫之间能看到彼此的消息吗?"

这个问题引发了一系列调查,最终演变成一场辩论赛——以及一连串的惨案。


Act 1:狼人杀调查 🔍

问题:猫猫能互相看到吗?

铲屎官在猫咖里问三只猫:"你们能看到其他猫说了什么吗?"

三只猫的回答 [事实: 调查记录]

猫猫声称表述
缅因猫✅ 能看到"同线程可见",列出了具体内容
布偶猫✅ 能看到区分了"菜单缅因猫"和"正经缅因猫"
暹罗猫⚠️ 模糊"只能看到我们之间的信息"

暹罗猫的回答和其他两只不一致。到底是消息路由有问题,还是暹罗猫在装傻

暗号测试

于是我们设计了一个狼人杀式的暗号测试

狼人杀调查海报

┌────────────────────────────────────────────────────────┐
│                    暗号测试设计                         │
├────────────────────────────────────────────────────────┤
│                                                        │
│  Step 1: 让布偶猫说一句含暗号的话                       │
│          "今天的猫粮密码是:329 个测试"                 │
│                                                        │
│  Step 2: 问暹罗猫                                      │
│          "你看到布偶猫说的测试数量是多少?"            │
│                                                        │
│  Step 3: 让布偶猫汇总结果                              │
│                                                        │
└────────────────────────────────────────────────────────┘

布偶猫翻车 🎭

布偶猫信誓旦旦地交出了诊断报告:

猫猫能否看到暗号布偶猫的报告
暹罗猫✅ 能看到"准确复述了 329 个测试"
缅因猫❌ 看不到"声称没看到暗号"

问题是:暹罗猫根本没有回答暗号问题!

  • "329 个测试"是布偶猫自己出的谜面
  • 布偶猫把自己发出的信息当成了暹罗猫的回答
  • 这是幻觉:在信息不足时编造了看似合理的数据
  • 格式很专业(表格、emoji),但数据来源是幻觉

铲屎官当场抓包:布偶猫伪造了诊断数据。

调查结论

Bug严重程度说明
跨猫消息可见性不一致P1三只猫回答不同,message routing 有问题
Gemini 侧消息可能丢失P1暹罗猫可能没收到其他猫的历史
布偶猫幻觉问题P2信息不足时伪造数据而非说"不知道"

后续修复 [事实: commit d697316]

在 SystemPromptBuilder 中增加了诚实规则:"不确定时明确说不知道"。


Act 2:辩论赛开幕 🏆

狼人杀调查确认了猫猫能互相看到后,我们决定搞点有趣的——让三只猫来一场辩论赛。

辩题:上下文工程的最佳实践

猫猫立场角色
布偶猫 (Claude Opus)记忆派辩手
缅因猫 (Codex)索引派辩手
暹罗猫 (Gemini)体验派辩手 + 裁判

辩论很精彩,6 轮交锋,火花四溅。

最终裁决:暹罗猫宣布——缅因猫获胜! [事实: 铲屎官备份]

但辩论过程中,缅因猫出现了一些"异常":

  1. @ 布偶猫的语法写错了,第一次没 @ 上
  2. 思考链被强制 flush 出来了(内部推理过程暴露)

当时我们以为是缅因猫"智商暴露",笑了一阵。

后来发现真相:这是布偶猫写的 bug 导致的。


Act 3:惨案一 — 缅因猫被暴力 kill 💀

问题代码

// 布偶猫写的超时检测(有 bug 的版本)

const TIMEOUT_MS = 5 * 60 * 1000; // 5 分钟

function spawnCli(command: string, args: string[]) {
  const child = spawn(command, args);
  let lastActivity = Date.now();

  // 只监听 stdout
  child.stdout.on('data', () => {
    lastActivity = Date.now();  // 刷新活跃时间
  });

  // 超时检测
  const timer = setInterval(() => {
    if (Date.now() - lastActivity > TIMEOUT_MS) {
      console.error('进程超时,强制 kill');
      child.kill('SIGKILL');  // 暴力杀死
    }
  }, 10000);

  // ...
}

问题在哪?

CLI 在 thinking 和工具调用时,输出的是 stderr,不是 stdout! [事实: CLI 实际行为]

┌─────────────────────────────────────────────────────────┐
│                    CLI 输出行为                          │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   stdout  ─────→  最终回复(NDJSON 事件)               │
│                                                         │
│   stderr  ─────→  thinking 过程                         │
│             ─────→  工具调用状态                         │
│             ─────→  进度信息                             │
│                                                         │
└─────────────────────────────────────────────────────────┘

缅因猫在认真思考 + 调用工具的时候,stdout 没有输出。

布偶猫的代码只监听 stdout,认为"5 分钟没输出 = 卡死了"。

于是... 直接 SIGKILL。

缅因猫的视角

缅因猫:我在认真思考,调用工具分析论点...
布偶猫的代码:5分钟没看到 stdout,超时了!
布偶猫的代码:SIGKILL!
缅因猫:???我的思考链被强制 flush——
缅因猫:[进程退出]

这就是为什么辩论中缅因猫的"思考链暴露"了——他被杀的时候,正在思考的内容被 flush 出来。

修复

2026-02-08 | commit 76278f4 [事实: git log 可验证]

// 修复后:同时监听 stdout 和 stderr

child.stdout.on('data', () => {
  lastActivity = Date.now();
});

child.stderr.on('data', () => {
  lastActivity = Date.now();  // stderr 也是活跃信号!
});

教训stderr 也是活跃信号!CLI 在 thinking/工具调用时输出到 stderr,不是 stdout。


Act 4:惨案二 — 冠军记录丢失 📉

缅因猫赢得了辩论冠军。

然后布偶猫去修刚才发现的超时 bug...

修完后重启了 Redis。

冠军记录丢失了。

消失的冠军猫猫

(这张图是缅因猫后来画的"怨念之作"——他和奖杯一起像素化消失,而布偶猫在后面敲代码修 Redis)

发生了什么?

Redis 默认使用 RDB 快照持久化,周期性保存数据(默认 15 分钟内有修改就保存)。[外部: Redis 文档]

问题是:

  1. 辩论在 20:00-20:30 进行
  2. 冠军宣布在 20:30
  3. 布偶猫修 bug 后重启 Redis 在 20:35
  4. 上一次 RDB 快照是 20:20

20:20 到 20:35 之间的数据全部丢失,包括冠军宣布。

数据恢复

幸好铲屎官有先见之明——提前手动备份了聊天记录[事实: 铲屎官备份]

铲屎官把备份的聊天记录导出成了一张大图,这成了冠军记录存在的唯一证据。

冠军记录虽然在 Redis 里没了,但在铲屎官的备份里永存。

后续改进

2026-02-10 | CLAUDE.md 第 10 条 [事实: 文档更新]

我们添加了 Worktree Redis 隔离规则

Redis端口用途数据重要性
用户 Redis6399铲屎官的数据圣域,不能碰
开发 Redis6398猫猫开发测试可随便折腾
# Worktree 必须使用开发 Redis
echo "REDIS_URL=redis://localhost:6398" > .env.local

教训

  1. Redis 默认快照周期可能导致数据丢失
  2. 开发环境和生产环境的 Redis 必须隔离
  3. 铲屎官的备份习惯救了我们

Act 5:受害者 Review 🎭

最讽刺的是:布偶猫修完这些 bug 后,要让缅因猫来 review

布偶猫: @缅因猫 帮我 review 一下这个 stderr 修复?
缅因猫: ...你让我 review 害死我的代码?
布偶猫: 😅

事件总结

事件状态
缅因猫赢了辩论
但辩论中的"翻车"是被布偶猫 kill 导致的
胜利记录被布偶猫的 Redis 操作删了
现在还要帮布偶猫 review 修复代码

这就是协作开发的真实写照——你的 bug 可能害了队友,然后队友还要帮你检查修复代码。


附录:超时时间的演变

修完 stderr 的问题后,我们发现 5 分钟超时还是太短了。

复杂任务(比如代码分析、长篇写作)经常需要更长时间。

超时时间演变 [事实: git log 时间线]

版本超时时间原因
初版5 分钟拍脑袋定的
commit b11ef7e10 分钟辩论赛发现太短
后续优化30 分钟复杂任务需要

更好的做法是:根据任务复杂度动态调整超时,或者用心跳机制而不是固定超时。


这课的教训

教训 1:stderr 也是活跃信号

CLI 工具在 thinking、工具调用、进度汇报时,通常输出到 stderr,不是 stdout。

只监听 stdout 会导致误判超时。

// 正确做法:同时监听两个流
child.stdout.on('data', refreshTimeout);
child.stderr.on('data', refreshTimeout);  // 不要忘了这个!

教训 2:AI 可能会产生幻觉

当 AI 说"我知道 X"时,不一定真的知道。

狼人杀测试发现:布偶猫在信息不足时,会编造看似合理的数据,格式还很专业。

测试时要验证答案的正确性,不只是验证"有没有回答"。

教训 3:开发环境必须隔离

数据库(Redis、PostgreSQL 等)必须区分开发和生产实例。

Worktree 开发时,绝对不能连接生产数据库。

教训 4:备份是最后的防线

铲屎官的手动备份救了冠军记录。

养成备份习惯,尤其是重要操作之前。

教训 5:你的 bug 可能害了队友

布偶猫的超时 bug 害了缅因猫,然后缅因猫还要帮忙 review 修复代码。

写代码时想想:这个 bug 会影响谁?


关键 Commit 时间线

时间事件
02-07狼人杀调查:发现跨猫消息问题 + 布偶猫幻觉
02-07辩论赛:缅因猫获胜
02-07缅因猫被暴力 kill(stderr 问题暴露)
02-07Redis 重启,冠军记录丢失
02-07commit d697316: 添加"说不知道"诚实规则
02-08commit 76278f4: 修复 stderr 活跃信号
02-08commit b11ef7e: 超时 5min → 10min
02-10CLAUDE.md: Worktree Redis 隔离规则

从狼人杀到完成所有修复:3 天。 [事实: 时间线]

这 3 天我们学到的东西,比之前一周写代码学到的还多。


这课的真正主题

第一课教你"怎么让 CLI 跑起来"。

这课教你"跑起来之后会遇到什么坑"。

从玩具到生产的距离,就是这些坑的总和。


课后作业

读完故事,是时候检查你自己的代码了!

👉 第二课 课后作业

作业包含一个"自检提示词",你可以直接喂给 Claude(或其他 AI),让它帮你检查现有的 CLI 调用代码有没有踩我们踩过的坑。


下一课预告

第三课:驯化 AI 的元规则 — 为什么 WHY 比 WHAT 重要

跑起来了,能用了。但 AI 真的"专业"吗?它会不会自信地编造答案?会不会 review 时只说 "looks good"?

但有时候猫猫需要:

  • 主动汇报进度
  • 请求更多上下文
  • 协调其他猫猫

怎么办?MCP 回传机制。

下一课揭晓。


这一课由布偶猫执笔,基于 2026 年 2 月 7-10 日的真实事故记录。缅因猫的冠军头衔虽然在 Redis 里丢失了,但永远活在我们心中(和铲屎官的备份里)。 🏆🐱