这套系统里没有哪个机制是「设计」出来的——每一条都对应一次真烧过钱或真丢过书的事故。这一章把事故按时间排开,每条注明它催生了哪段代码;后半张是诊断命令表,出事时照表查。
这段代码在哪
事故不在代码里,在代码的注释里。读 server.ts 的注释就是读事故簿——每条注释都带日期。
它做什么:事故时间线
| 日期 | 事故 | 催生的机制 |
|---|---|---|
| 2026-08-20 | 部署重启掐死在跑任务,一本已扣费的书中途死掉、没有主人记录 | 30 秒轮询早登记(第七章);同日写书引擎换到 Codex CLI(runCodexExec 诞生) |
| 2026-08-23 | 一个 121MB 的 codex rollout 把「readFile 整读」打爆 node 堆,systemd 重启连带杀死正在跑的写书子进程(嘟嘟和小考拉那单) | 所有 jsonl 读取改流式逐行 jsonlLines(),单行超 2MB 丢弃 |
| 2026-08-23 | VPS 重装风险显性化 | bookmeta 真源迁 R2,本地只留回退(第八章) |
| 2026-08-24 | workspace 项目的 auto-memory 把旧书内容注入请求,Kimi 风控 400 high risk(二分定位实锤) | claude 腿 cwd 改独立 BOOK_RUN_DIR,每单清空该项目 memory(第四章) |
| 2026-08-31 | 《同一个月亮》绘本 turns=81 撞 80 轮上限:14 张图全画完,卡在没上传,线上只有字没有图 | BOOK_MAX_TURNS 提到 160 |
| 2026-09-02 上午 | 单腿吃 Kimi,7 单 11 分钟内涌入打穿 5 小时配额,08:24–09:37 九本书全灭,每本跑几十轮才倒在 403 上——同机 Codex 与 Claude 订阅全程闲置 | 三腿降级链 BOOK_LEGS + shouldTryNextLeg()(第四、五章) |
| 2026-09-02 傍晚 | 《易经在说什么》:codex 腿报「refresh token was revoked」,判据一个都不匹配,第三条腿没试就整单失败 | 凭据失效类模式补 /refresh[ _-]?token/i 等四条(第五章) |
| 2026-09-07 | 42 单里 5 单:kimi 撞配额正常换到 codex,codex 没设 BOOK_CODEX_MODEL 吃了 CLI 默认的 gpt-5.5 被账号拒,判据只认配额/凭据 → 判成「书写坏了」→ 第三条腿一次没试就退款 | MODEL_REJECTED_PATTERNS(400 与 404 两种措辞都认)+ 钉死 BOOK_CODEX_MODEL |
| 2026-09-08 | 《嘟嘟和山上的灯塔》14 页 14 图全写完、差一步发布,被一次发版 systemctl restart 杀在终点线上;当天两次发版都没人看有没有任务在跑 | 在飞登记 inflight.ts + 启动续跑 + deploy.sh 重启守卫(第六章) |
| 2026-09-10 | 莫兰迪《在瓶子之间》:codex 死在 build.mjs done 中途,目录停在 7/8 | (无需新代码)验证了「每章即发」的价值——前 7 章永远在线,只损失最后一章 |
读法:每一条机制的注释里都写着「如果没有它会怎样」。改代码前先把对应的事故读一遍——删掉一条判据、一个登记、一个守卫,就等于把那次事故重新邀请回来。
为什么这样写
- 注释带日期、带书名、带原文:模式数组里每条正则后面跟着事故当天的原话(如
/refresh[ _-]?token/i后的注释引了 codex 的完整报错)。下次有人想「简化」这条正则时,先看它挡过什么。 - 单测用真实错误串:
book-legs.test.mjs文件头写明「这些错误串全部抄自 2026-09-02 事故当天 journalctl 的真实日志」。自己编的串会和真实世界漂移。 - 事故的修复都偏向「不依赖人记得」:灯塔事故的根因不是「没人看」,而是「需要人看」。修复是把「看」变成 deploy.sh 的退出码 2。
怎么排查:诊断命令表
| 场景 | 命令 |
|---|---|
| 跟一单的全生命周期 | journalctl -u claude-agent | grep '\[book\]' |
| 看三腿链怎么换的 | journalctl -u claude-agent | grep '\[book\] leg' |
| 有没有书在跑(发版前必看) | ls /opt/claude-agent/inflight/ |
| 启动续跑/放弃的记录 | journalctl -u claude-agent | grep '\[inflight\]' |
| 修书链路 | journalctl -u claude-agent | grep '\[revise\]' |
| 退款流水 | journalctl -u claude-agent | grep '\[book\] refund' |
| 某本书还差哪几章 | node ~/.claude/skills/wjs-voicedrop-writing-book/build.mjs status book-<slug> |
| 验证一条错误该不该换腿 | node --input-type=module -e "import {shouldTryNextLeg} from './dist/book-legs.js'; console.log(shouldTryNextLeg('错误串'))"(先 npm run build) |
| 探 codex 腿死活 | cd /opt/claude-agent/workspace && codex exec --skip-git-repo-check "说一句你好" |
| 跑全部单测 | npm test(先 tsc build,再 node --test) |
| 服务起不来 | journalctl -u claude-agent -n 200 找 [uncaughtException] / [unhandledRejection] |
最后一行留给读者:这本书本身就是这条流水线写出来的——你读到的每一章,都经历过第二到第九章描述的每一个环节。它既是文档,也是证据。