一本书是怎么被写出来的 书架

第 06 章 / 共 10 章

第六章 · 在飞登记

书是在本进程后台跑的,systemctl restart 一下子进程全死。在飞登记(inflight)是应对这件事的机制:每单开工时在 inflight/ 目录落一个 JSON,引擎跑完立刻删。于是「启动时目录里还有文件 = 上次没跑完的孤儿」,服务一起来就把它续上。

这段代码在哪

它做什么

登记里存的是「重跑这一单的全部再现参数」:

字段写书单(InflightCreate)修书单(InflightRevise)
标识jobIdslug + entryTs
内容seedinstruction
共有scope、author、auth(下单 bearer)、startedAt、attempts

落盘的写法是两步:先写临时文件再 rename,权限 0600:

export async function writeInflight(dir: string, rec: Inflight): Promise<void> {
  await mkdir(dir, { recursive: true, mode: 0o700 });
  const p = inflightPath(dir, rec);
  const tmp = p + ".tmp";
  await writeFile(tmp, JSON.stringify(rec, null, 2) + "\n", { mode: 0o600 });
  await rename(tmp, p);
}

生命周期对应的日志:

时刻动作日志
开工writeInflight(失败只留日志,不挡开工)(静默)
引擎返回removeInflight——成败都销档(静默,后续是 [book] done)
启动后 5 秒resumeInflightJobs() 扫目录[inflight] 发现 N 个上次没跑完的任务 / [inflight] 续跑 …(第 N 次)
超过次数封顶giveUpInflight():标 failed + 退款 + 告警 + 销档[inflight] … 已拉起 N 次仍未完成,放弃

次数封顶只有两行:

/** 原跑 + 续跑各算一次;第 3 次不再试——两次都没跑完,多半不是「被打断」而是书本身有问题。 */
export const INFLIGHT_MAX_ATTEMPTS = 2;

resumeInflightJobs() 的关键顺序是「先记次数再开跑」——rec.attempts += 1 先落盘再 startBookJob(rec)。这一跑再被杀,下次启动读到的 attempts 已经是加过的,不会无限重跑。

deploy.sh 那一半守卫是 bash:

busy="$(inflight_list)"
if [ -n "$busy" ] && [ "${FORCE_RESTART:-0}" != "1" ]; then
  if [ "${WAIT_FOR_IDLE:-0}" = "1" ]; then
    echo "▸ 有书在跑,等空窗(最多 ${MAX_WAIT_MIN} 分钟):"; echo "$busy" | sed 's/^/    /'
    …
  else
    echo "✗ 有书在跑,不重启(代码已同步,正在跑的进程仍是旧版):"
    echo "$busy" | sed 's/^/    /'
    exit 2
  fi
fi

注意设计:代码同步永远做,restart 才看 inflight。rsync 不影响运行中的进程;非空时退出码 2 并列出在跑的书,给两条出路——WAIT_FOR_IDLE=1 每 30 秒看一次、最多等 MAX_WAIT_MIN(默认 90)分钟;FORCE_RESTART=1 立刻杀(新代码会把被杀的书续跑,但前一条腿烧掉的轮数收不回)。

为什么这样写

怎么排查

要确认什么命令
现在有没有书在跑ls /opt/claude-agent/inflight/(空 = 空闲,可安全 restart)
这单是谁、第几次cat /opt/claude-agent/inflight/*.json(0600,需 claude-agent 身份或 root)
启动续跑了没有journalctl -u claude-agent | grep '\[inflight\]'
发版被守卫拦了看 deploy.sh 输出的书名清单;确认后可 WAIT_FOR_IDLE=1 ./deploy.sh
一本书是怎么被写出来的 · 王建硕
费曼式写法 · 由多个 AI 代理撰写与互相审校