Skip to content

fix(codex-app): 修复假忙/假闲 + Riff 关停/PM2 恢复#537

Open
xiaoxueSunn wants to merge 4 commits into
deepcoldy:masterfrom
xiaoxueSunn:fix/codex-app-false-busy
Open

fix(codex-app): 修复假忙/假闲 + Riff 关停/PM2 恢复#537
xiaoxueSunn wants to merge 4 commits into
deepcoldy:masterfrom
xiaoxueSunn:fix/codex-app-false-busy

Conversation

@xiaoxueSunn

@xiaoxueSunn xiaoxueSunn commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

问题

Codex App 路径下有三类用户可见故障:

  • 任务仍在执行,飞书卡片和 Dashboard 已显示「等待输入」;
  • 任务已经结束,界面仍停在「工作中」,后续消息只能排队;
  • 任务长时间没有可观察进展,界面仍显示为正常工作中;
  • 新消息进入错误轮次,或已结束轮次继续接收消息。

同一批排查还发现,Riff 关停可能留下半关闭状态,PM2 恢复可能把旧实例误认为新实例已就绪。

根因

旧实现允许 CLI 画面、worker 内存、runner 通知和进程管理器分别推断当前状态,且这些来源可以互相覆盖。输入链路也缺少统一的持久化、提交与 ACK 边界,异常时可能发生重复提交、丢失、插队或错误归属。

修复

  • Codex App 的 runner signed state 成为 ready / busy 的权威事实源;飞书卡片和 Dashboard 只做状态投影。
  • runner 通过 submitted / progress / completed 维护 liveness;持续无进展时显示「长时间无进展」,不继续冒充正常运行。
  • 输入先进入 daemon 持久化 journal,再按稳定 client user message ID、turn ID 和 dispatch attempt 结算。
  • 文本与 Enter 都安全送达后才确认;缓冲区、轮次或 generation 无法证明干净时保持阻塞,不猜测、不自动跨代重放。
  • Riff 引入 prepare / commit / abort 关停围栏。
  • PM2 恢复核对精确 generation、descriptor 和 supervisor 状态,旧实例不能冒充新实例。

变基与验证

本 PR 已重新变基到 origin/master@cb556211,head 为 72783ba8

在独立、干净的 PR1 worktree 中:

  • git diff --checktsc --noEmit 通过;
  • public domain audit、TypeScript build、Dashboard bundle 和 dist audit 通过;
  • 全量单测 666 个文件通过、1 个跳过;10,594 项通过、23 项跳过,失败 0。

未执行 daemon 重启、真实飞书 live E2E、Riff 远端任务和 PM2 生产式恢复演练;这些项目阻塞发布验证,不阻塞代码审核。

后续 PR

普通 Codex / RPC 生命周期修复以本 PR 为前置:PR #571。请先审核本 PR,再处理第二个 PR。

详细说明:https://bytedance.larkoffice.com/docx/TZUydIskCoJZT8xfMXzc2WI5ntc

本次未执行合并。

@xiaoxueSunn
xiaoxueSunn requested a review from deepcoldy as a code owner July 20, 2026 13:40
@deepcoldy
deepcoldy force-pushed the fix/codex-app-false-busy branch from 5ef85e5 to a980cf8 Compare July 22, 2026 03:21
xiaoxueSunn and others added 2 commits July 23, 2026 19:10
…overy (PR 1-5)

Fixes three user-visible failures in the Codex App path:
- False idle: task still running but card/Dashboard show idle
- False busy: task finished but UI stays busy
- Wrong turn ownership: message routed to incorrect task turn

Root cause: three state sources (TUI screen, worker memory, runner
notifications) could override each other. Fix makes runner signed state
the single owner of idle/ready; display layer only projects it.

Also includes Riff shutdown (prepare/commit/abort fence) and PM2
recovery (exact start, descriptor guard, supervisor shutdown protocol)
as separate concerns with independent test gates.

Tests: 29/29 + 32/32 false-busy/false-idle cases pass; full unit
regression shows 0 new failures. tsc --noEmit passes.

Doc: https://bytedance.larkoffice.com/docx/TZUydIskCoJZT8xfMXzc2WI5ntc

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #537 独立复审结论(head 72783ba

结论:REQUEST CHANGES

审查期间 head 从 a980cf8 被推进到 72783ba(rebase 到最新 master cb55621,现已 MERGEABLE)。我对两 head 的 PR-diff 做了归一化 interdiff 人工核对:这轮 rebase 是纯 master 语义适配、无夹带(read-isolation capabilities marker 合成、deferred-topic-send × revalidateIsolatedOriginBeforeEffect 编织、types 双方字段联合、logicalContent 合并守卫等均正确),全部 findings 平移适用。

工程质量总体罕见地严谨(世代化 spawn、签名控制通道、三段式 Riff fence、处处 fail-closed),核心互斥/签名/账本机制未找到可攻破路径。问题集中在集成缝与「拆掉超时逃逸却没补有界性兜底」


Blocker(必须修复才能合并)

B1. Lark 事件层:新硬死锁 + 整 bot 冻结族

  1. seedRoutingGate × capMs=0 ingress lane 死锁(event-dispatcher.ts:2624 / 2947 / 2964):话题种子 S 与其 root 回复 R 乱序到达(重启积压重推/6h re-push/WS 重连)时,R 持有 chat 级 lane(capMs=0 无逃逸)await S 的 gate,S 排在 R 之后永不执行 → 整群永久卡死。gate 的 ready 只能由 S 的 handler resolve,无超时兜底(已核 2126-2145)。放大项:claimMessageOnce 在入队前持久化 → 卡死期间所有消息被 claim,重启后 dedupe 拒重放 → 跨重启永久丢消息。本 PR 新引入(基点两者并发执行无此死锁)。修法:gate 等待加界(Promise.race + forwardFollowupWaitMs 口径)或检测 gate 完成者排在本任务之后时跳过等待。
  2. 挂死 admission × 60s 周期 sweeper mutation = 整 bot 冻结(bot-turn-mutation-gate.ts:152-154 × daemon.ts:18236-18239 × idle-worker-sweeper.ts:96-102):任一 admitted handler 永久挂起(Lark SDK Client 未配超时,网络静默断连即可)→ sweeper 的 runDetachedBotTurnMutation 置 mutating=true 后无界等 drain → 该 bot 全部新消息/卡片/scheduler 排队至重启。基点行为仅该 anchor 延迟 5s。修法:sweeper 改用已有的 tryWithBotTurnMutation 超时放弃本轮;给 Lark Client 配请求超时。
  3. 同族:canonical FIFO capMs=0 拆掉了 bd2bddd 修真实生产事故(「上一条没触发到你」)时加的 5s 兜底,且只改了人类路径——bot 发送方路径(2213/2344)仍是 5s 逃逸,serializer 注释点名的原始场景(bot dispatch 的 /repo prime>5s + kickoff)原样保留;同时 self-/close、bot @mention、stale-seed-flush、paired-forward 四条路径在 lane 内 await 完整 handler → bot 编排群(多 bot 协作正是核心场景)整群串行数秒。修法:四处统一「同步入队即释放 lane」模式 + cap 语义统一 + anchor-serializer 过期注释更新。

B2. PM2 restart/升级边界在生产规模必然翻车(CLI 侧与 Riff 侧两个独立审查代理同时命中)

  1. verify 预算 10s 覆盖不了 30+ bot 冷启(cli.ts:263 / 3340-3358;能力位是 daemon boot 最后一步 commit,排在 restoreActiveSessions 等所有耗时步骤之后)→ 超时进 rollback:daemon 已 attest 则优雅关停并 delete 刚拉起的整个 fleet;未 attest 则 rollback 预检 throw 报「rollback failed」(fleet 实际还在启动)。两种结局都错。修法:预算按 bot 数伸缩(≥60s)/能力位发布提前到 SIGTERM handler 装好即发/verify 超时降级为警告不 teardown。
  2. 首次升级边界三路全堵(pm2-shutdown-capability.ts:44-52 + pm2-start-transaction.ts:84-104):线上旧 daemon 无能力位、旧 PM2 行无 stop_exit_codes:[42] → 部署本 PR 后 botmux restart/stop/start 全部拒绝;错误信息指向的「operator-approved one-time manual bootstrap」在 CLI 里不存在(已全文 grep)。修法:提供显式一次性 bootstrap 命令/flag + 部署 runbook,错误信息给出确切命令。
  3. fleet-shutdown 补偿预算过紧(fleet-shutdown.ts:208-213):projection cap 1.2s,每次冷启 pm2 jlist 子进程在 30+ daemon 同退的 CPU 风暴下常态超过 → 「fleet state unverified, no compensation attempted」:旧 fleet 已灭、新 fleet 未起、不补偿。修法:cap 下限 ≥5s + 超时重试。

B3. doc-comment 分流回归:波及全部非 codex-app CLI(cli.ts:7232 / 7360 / 7577)

exactOriginDispatch 唯一来源是 codexAppDispatchLedger(仅 codex-app 写入),doc-comment 分流硬性要求 deliverySink === 'doc_comment' → Claude/Gemini 等 20+ CLI 的 /watch-comment、/subscribe-lark-doc 会话里 botmux send 不再分流,落进 sendMessage(appId, 'doc:xxx', …) → 飞书 400 直接失败(基点对所有 CLI 均分流,已对照确认)。修法:无 ledger 时回落 docCommentTargets[originTurnId] 分流(沿用新的 originTurnId 权威与 payload 校验)。


Major(强烈建议随本 PR 修)

  • M1 codex-app-control.ts:1188-1190 — v2 兼容分支恒真式:查的是 record 自身 generation(校验已保证 undefined)而非目录 identity 的 generation → 混版本窗口 v2 记录匹配任意同 dev+ino 的 v3 目录,generation-marker 防护对 v2 静默失效(假阻塞 10s→spawn 失败,代理已实测复现)。一行修:identity.generation === undefined
  • M2 codex-app-control.ts:1007-1010 — 双 generation marker 抛普通 Error 穿透 retry → lease 目录永久毒化无自愈(SIGKILL 落在 mkdir 与 marker 写入的 1s 窗口即可制造,代理实测复现),违背自身 SIGKILL 可恢复设计。修法:多 marker 状态可回收化。
  • M3 codex-app-runner.ts:703-769, 1119 — identity conflict 只发 diagnostic 不 settle turn.done(已核)→ runner FIFO 永久卡死 = 永久假忙,恰是本 PR 要消灭的形态。修法:冲突时发带说明的 error-final 让 FIFO settle。
  • M4 worker.ts:7695 之后 — spawn generation 守卫只盖了 prepareCodexAppControlGeneration,紧随的 prepareCliPluginGenerationAndGateway await 后与 backend.spawn 前无重查 → restart 竞态双 spawn(基点竞态残留,PR 自建的 CliSpawnSupersededError 机制覆盖不全)。补一行 generation 重查。
  • M5 worker.ts:10684-10707 — codex RPC hybrid FRESH 下 queued activation token 无 ACK 发射点(prompt 被 turn/start 预发、不进 pendingMessages)→ admission gate 恒真,后续消息全部进 tail 永不投递(消息黑洞)。RPC hybrid 默认 OFF 故未列 blocker;修法:非入队消费路径补发 queued_activation_submitted,或铸 token 前断言 ACK 可达。
  • M6 command-handler.ts:1643-1682 × daemon.ts:16273 — a980cf8 启用 pendingRepoCommitInFlight 后,文字 /repo 提交窗口(sessionReply+deleteMessage 0.2-2s)内的同 anchor 消息进 pendingFollowUps,而 pty 会话此后无任何消费路径 → 静默吞消息 + 残留 buffer 可能陈旧重放。修法:finally 清 flag 时 drain 新增 followUps。
  • M7 worker.ts:8823 × managed-origin-capability.ts:521 — macOS 读隔离下 hasMatchingManagedOriginCapability 无 channelId 形参、relay 恒 null → ready-gate 预检恒 false,静默降级 readyPattern + 每次 spawn 噪音日志(明确回归;daemon 生产在 Linux 故非 blocker)。
  • M8 dashboard-ipc-server.ts:3049-3090 — 读隔离开关 teardown 探测对含 closed 在内的行做 kill(pid,0),crash 残留 pid 被复用后恒「活」→ 409 永久卡死开关且无自愈;另 legacy 行触发同步 tmux/herdr/zellij 探测阻塞事件循环。修法:只探 active 行 / pid 加出生身份校验 / close 统一清 pid。
  • M9 dashboard-ipc-server.ts:2653 等 4 处 — protected-ownership 守卫把 queued===true(待办池)和 pendingRepoSetup 也算保护态且对所有 CLI 生效:任意 bot 挂一张待办卡即无法 dashboard 切 agent,错误码却叫 codex_app_dispatch_pending。修法:非 codex-app 变更收窄守卫或独立错误码+列出阻塞会话。

Minor(可 follow-up,摘要)

  • codex-app:turn/started 预接受缓冲按 method 而非 turnId 去重(Goal continuation 先到会顶掉真 started);.retired-* 残留无清扫;steer 拒绝×completed 晚到窗口;控制队列 2048 触顶自杀(长断连下杀死可 warm-reattach 的 CLI)。
  • /repo:flag 置位→return 间抛出则永久泄漏(置位挪最后);command-handler.ts:1814 悬空 deleteMessage 无 catch(daemon 无 unhandledRejection handler,可达即崩);卡片路径不置 flag 的双入口不对称。
  • 关停链:explicit_close_in_progress 双语义错误串(backend 级可达时 worker 永久持 stale requestId);任一 Riff 会话有排队输入即拒整机 shutdown(一个会话可无限阻塞 fleet 重启);SIGTERM 1s 抢锁失败即放弃优雅停机(高负载下恰好丢掉三段 drain);成功 shutdown 路径卡在 gate 的消息静默消失(~14s 窗口);restart-report 45s 等待窗与 verify 预算无共享常量;混版本 intent 消费;supervisor client 整体超时把已 ACK daemon 一并判失败。
  • CLI:pm2-jlist 尾部噪音判 malformed;descriptor-guard fresh 分支缺出生身份复核(PID 复用 90s 误判窗);picker stdin async 重入;send-dispatch hookOrigin 单独缺席静默丢;sleepSyncMs SAB 兜底 no-op。
  • worker:crash-loop respawn 被 superseded 时静默吞触发消息;ACK 后 promote 失败×worker 退出卡死 initialStartPending;transferSession 终段绕 per-key 锁;initPromptMaterialized 未挡 flushPending;混 build 窗口硬失败面(release note 注明)。
  • store:deferredIntent 新鲜度锚容器 at(围栏期 update 报告可静默丢);persistActiveRiffLineageExact 宽松 reader 把 IO 失败误类为 retain_fence;queuedActivationTail 无上界;closeSession 附件簿记先删后清理;daemon 启动×离线 CLI 写残余竞态;macOS 旧 pane 升级后 fail-closed 需重建(PR 描述注明);sandbox-store staged API 注释失实。
  • 其它:v3 session-relay-client.ts:103 端口优先级翻转(spawn-time env 压过在线 discovery,daemon 重启端口漂移后 v3 mutation 打死端口;应 claim.ipcPort ?? discovered ?? envPort);restore 屏障连 supervisor shutdown 路由一起挡(restore 卡死时救场通道不可达);gate fail-open 无强制/遥测。

验证记录

  • 两个 head 均 pnpm build 绿。
  • 全量测试(BOTMUX_SCHEDULE_TIMEZONE=America/Los_Angeles):老 head 648/649 文件(唯一败 v3-distillation×6 = 本机既有基线,PR 未触及);新 head 665/667,失败 = v3-distillation×6 + session-discovery.smoke×2(纯 master cb55621 复现,既有基线)+ workflow-v3-ephemeral-pool×1(flake:单跑绿、master 侧新测试)。无 PR 归因的新增失败
  • 7 个模块并行对抗审查(lease/worker/daemon 并发/Riff+PM2/store/CLI 侧/adapters)+ 全部高严重级 finding 由我逐一二次核实(代码直读/基点对照/调用链追踪);两个代理对 B2 独立撞车互证。
  • 正面清单(未发现问题):lease v3 抢锁原子性与 TOCTOU 防护、Ed25519 signed state 不可伪造性、dispatch ledger 有界性与 exactly-once、store 白名单/own-write/无锁 RMW 三大历史坑逐项未踩、managed-origin attestation 全面 fail-closed 且权限只收窄、session-mutation-guard 覆盖面、anchor FIFO 入队同步性与 GC 身份守卫、riff 三段 fence crash 点逐一可恢复、PM2 事务文件锁与出生身份世代绑定、共用 adapter 契约全 optional 无跨 CLI 误分支。

Copy link
Copy Markdown
Owner

修复进度:已启动(head 72783ba8

已读取并核对本轮 CHANGES_REQUESTED。当前没有 inline review thread,反馈集中在一次顶层 review;本轮按以下范围处理:

  1. B1 Lark 事件层:seed gate/ingress lane 死锁、sweeper mutation 无界等待、human/bot canonical FIFO 策略不一致。
  2. B2 PM2 restart/升级:大 fleet verify 预算、首次升级 bootstrap 缺口、fleet shutdown projection 补偿预算。
  3. B3 doc-comment 分流:恢复非 Codex App CLI 的 origin-target fallback。
  4. M1–M9:lease v2/v3 匹配与双 marker 自愈、identity conflict settlement、spawn generation、RPC hybrid activation ACK、repo commit 窗口 drain、macOS managed-origin channel、read-isolation teardown PID、dashboard ownership guard。

Minor 先作为 follow-up 候选,不与 blocker/major 混在本轮提交中;若修复 blocker/major 时自然覆盖,会在最终摘要单列。

执行顺序:先补可复现回归测试,再分组修复;随后运行 pnpm build、相关专项测试和隔离环境的全量单测。完成后会在本 PR 更新逐项处置、测试结果和新 head,并重新请求独立复审。

Copy link
Copy Markdown
Owner

修复进展(阻塞项阶段):

  • B1 Lark:raw chat ingress 恢复 5s 有界 HOL;seed routing gate 增加独立超时;bot sender / paired / stale-forward 路径改为同步入 canonical FIFO 后释放 raw lane;bot 与 human canonical FIFO 统一为严格顺序;idle sweeper 改用有界 mutation acquisition,SDK HTTP 请求统一 15s timeout。
  • B2 PM2:启动验证预算改为至少 60s,并按进程数动态扩展;fleet shutdown 总预算增至 60s,生产 projection/compensation 预算增大并允许一次 transient jlist retry;新增显式双确认的一次性首次升级入口 botmux restart --bootstrap-shutdown-protocol --yes,普通 stop/restart 继续 fail-closed,且 bootstrap 每次 delete 前复核 name + pm_id + PID + process birth。
  • B3 doc-comment:非 Codex App CLI 恢复 per-turn docCommentTargets 分流;Codex App 仍只认 exact dispatch ledger,不允许降级。

当前验证:

  • pnpm build 通过。
  • 阻塞项定向测试通过:event-dispatcher 237、fleet-shutdown 36、shutdown-supervisor 19;此前同组 anchor/idle-sweeper/bot-registry/cli-send-hook 测试也通过。
  • 下一阶段处理 M1–M9,并继续补定向回归。

@deepcoldy

Copy link
Copy Markdown
Owner

修复进展(复审 blocker + major 已完成,待推送)

针对 CHANGES_REQUESTED @ 72783ba8 的 3 组 blocker 与 M1–M9,已完成代码和回归测试:

  • B1 / Lark 有界串行化:canonical anchor FIFO 保持严格顺序;raw ingress 恢复有界退出;seed gate 增加有界等待;bot/self/paired/stale-forward 路径不再长期占 raw lane;idle sweeper 改为 1s 有界 mutation acquisition;官方 SDK HTTP 全局加 15s timeout。
  • B2 / PM2 restart 与升级边界:启动校验预算改为 max(60s, 2s × processCount);新增显式首次升级命令 botmux restart --bootstrap-shutdown-protocol --yes(精确绑定 name + pm_id + PID + process birth,逐项重验);fleet shutdown 预留补偿预算并重试一次 transient projection failure。
  • B3 / doc-comment:非 Codex App CLI 恢复使用 frozen docCommentTargets 分流;Codex App 仍只信 exact dispatch ledger,避免降级绕过。
  • M1–M9:修复 v2/v3 POSIX 代际恒真式、双 generation marker 自愈、identity conflict 永久 busy、spawn generation await 缺口、fresh RPC activation ACK、repo commit 窗口吞消息、macOS read-isolation channel capability、closed PID 复用/legacy 同步探测、dashboard generic pending work 被误报为 Codex App(现返回 session_mutation_pending + blocking session/reasons)。

验证

  • pnpm build:通过。
  • 受影响测试:通过(含 event-dispatcher、idle sweeper、PM2/fleet shutdown、Codex control/runner、worker spawn/RPC、dashboard/session store、doc-comment、read-isolation 等)。
  • 全量:TZ=UTC BOTMUX_SCHEDULE_TIMEZONE=UTC pnpm test
    • 10628 passed / 1 skipped
    • session-discovery.smoke 2 项失败:测试把 PID 2000000 假定为不存在,但该宿主恰有此 PID;Claude 复审已在纯 master 复现为既有环境基线。
  • 当前机器 ~/.botmux/config.json 配置 scheduleTimeZone=Asia/Shanghai;未隔离时 3 个“默认=host local” scheduler 测试会受用户配置污染。显式隔离后相关 143 项全绿,本 PR 未改 scheduler 文件。

下一步:整理 commit、确认作者远端 head 未漂移后安全推送,再请求 Claude 基于新 head 独立复审。

@deepcoldy

Copy link
Copy Markdown
Owner

修复已推送

作者分支在本轮修复期间从 72783ba8 前进到 129686eemake replayed control settlement idempotent)。我没有覆盖该提交,而是:

  1. 拉取并核对新增提交(仅 codex-app-control / worker / 对应测试);
  2. 将本轮修复 commit 干净 rebase 到 129686ee
  3. 在组合 head 上重新执行 build 与重叠/受影响测试;
  4. 用精确 lease 将新 head 141cd637d 推送到作者分支。

组合 head 验证:

  • pnpm build
  • 17 个重点测试文件:734 passed / 1 skipped ✅(735 total)
  • 全量基线仍为 10628 passed / 1 skipped,仅宿主 PID=2000000 导致的 session-discovery.smoke 2 项既有失败。
  • pnpm daemon:restart 做了正常路径验证:在任何 signal/PM2 mutation 前因旧 fleet 的 PM2 policy fail-closed;未执行破坏性的 bootstrap。该实际错误路径现也直接给出可复制的一次性命令 botmux restart --bootstrap-shutdown-protocol --yes,并要求先人工确认所有 Session/Riff idle。

请基于 141cd637d 重新 review;重点建议复核 B1 canonical/raw 双层串行化、B2 bootstrap 精确身份重验与 fleet 补偿预算、M1/M2 POSIX 多代际恢复、M3 identity-conflict settlement,以及 M9 generic mutation blocker 的错误码/会话明细。

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #537 delta 复审(head 141cd63,基于已审 72783ba + 修复 delta)

结论:APPROVE

上一轮 REQUEST CHANGES 的 3 组 blocker + 9 major 已逐项验证修复到位;作者中途新增的 129686e(幂等 replay settlement)与修复 commit 无交叠区、组合无回归。

逐项验证

B1 Lark 事件层

  • 死锁:waitForSeedRoutingGate 以 forwardFollowupWaitMs 为界,超时警告继续(event-dispatcher.ts:2126)——乱序种子/回复不再互等 ✓
  • bot 路径(self-/close 2229、bot @mention 2360)改「同步入队 canonical FIFO(capMs=0) + 立即释放 lane」,与人类路径统一,5s 逃逸不一致消除、整群串行消除 ✓
  • 外层 ingress lane 恢复默认 5s cap(2972 移除 ,0)——lane 级永久卡死类风险消除 ✓
  • sweeper 改 tryWithBotTurnMutation(1s 有界,超时跳过本轮)(idle-worker-sweeper.ts)✓
  • Lark SDK 全局 15s HTTP 超时(bot-registry + client 探测客户端双路径)封掉已知无界 await 源头 ✓
  • stale seed 分支改异步双入队(先 seed 后当前消息,同步 append 保序)✓
  • 残余观察(P3 不阻塞):canonical FIFO 仍为无界严格 FIFO——若 handler 在 SDK 超时之外的路径挂死,单 anchor 仍会滞留(claim 已持久化);滞留面已从整群收敛到单 anchor 且已知源头有界。stale seed 分支在 sessionsReady 未决的启动窗口有极窄同 anchor 重排窗(需 mention-mode 变更×启动×burst 三重合)。

B2 PM2 链

  • verify 预算 max(60s, 2s×进程数),restart/start/start-bot 三处应用(cli.ts:267-276)✓
  • botmux restart|stop --bootstrap-shutdown-protocol --yes 真实实现(cli.ts:3170 bootstrapDeleteAllBotmuxProcesses):逐行 name+pm_id+PID+出生身份 CAS、每次 delete 前重读复核、世代变化即拒、legacy PM2 home 同护、拒与 --include-pm2 组合;两处错误文案与 help 均指向确切命令 ✓
  • fleet 补偿预算生产分档(reserve 10-25s / projection ≥5s / exact-start ≥10s)+ projection 双尝试重试 + FLEET_DAEMON_EXIT_WAIT 35→60s ✓

B3 doc-comment

  • isOriginDocCommentTurn = ledger 判定 || (!exactOriginDispatch && cliId!=='codex-app' && docTarget)(cli.ts:7551-7556):非 codex-app CLI 恢复分流且 payload-shape 校验同覆盖;codex-app 保持 ledger 独占 fail-closed(settled 后不回落可变态)✓

M1-M9

  • M1 identity.generation === undefined 一行修(codex-app-control.ts:1314)✓
  • M2 多 generation marker 可恢复:observe 返回全 marker 及逐 marker 双 lstat 验证;acquire 循环先证死全部 actor(live/fresh→retry)→grace→retireAmbiguousPosixDirectoryGenerations 逐 marker inode CAS rename-out(随机 retired 后缀防覆盖)→rmdir;失败走既有 retry 预算,有界收敛 ✓
  • M3 reportIdentityConflict 现发显式 error-final(稳定 client turn id、不复用 native 文本)、清 nativeActiveTurnId、settle turn.done——FIFO 不再永久卡死 ✓
  • M4 spawn generation 重查补两处:plugin-gateway await 后(worker.ts:7987)+ control bootstrap 准备前(9061)✓
  • M5 RPC hybrid FRESH accepted 分支补发 queued_activation_submitted(worker.ts:541-551,daemon 侧 worker-pool.ts:4142 按 sessionId+token 校验)✓;残余:ambiguous outcome 仍无 ACK——罕见降级态,tail 持久化、重启重放,恢复面为 notify/web terminal,fail-closed 非静默黑洞,可接受
  • M6 daemon.ts:16370 从缓冲条件移除 pendingRepoCommitInFlight:提交窗口普通轮直达已 fork worker,不再进无消费路径的 source buffer;flag 收窄为二次 repo 选择互斥(比我建议的 drain 方案更简洁且正确)✓
  • M7 hasMatchingManagedOriginCapability 增 channelId 形参,worker 传 readIsolationOriginChannelId(worker.ts:9351)✓
  • M8 双端修:closeSession 原子清 pid(含失败回滚恢复 priorPid)+ toggle 移除 pid 探测与 legacy 三 CLI 同步 fan-out(顺带消除事件循环阻塞)✓
  • M9 protectedSessionMutationReasons 8 种 reason 枚举;仅当全部 reason 为 codex dispatch 才报 codex_app_dispatch_pending,否则 session_mutation_pending + blockingSessions(sessionId/cliId/reasons) 列表 ✓
  • 附带:上轮 minor「turn/started 缓冲按 method 去重」也已修为 method+turnId(codex-app-runner.ts:809-811)✓

作者 129686e 组合检查

  • CodexAppControlRecordApplicationGate:per-(generation,seq) 单飞行 application,release 置于 replay-window commit 之后/拒绝路径——关闭「resolution→replay-commit」间隙的双重应用竞态;accepted(start/chunk)早退不进 gate 与不承诺 ACK 的语义一致
  • projectCodexAppControlReadinessStatus:签名 accepting-input 状态观测前 idle 一律投影为 working——与「runner signed state 唯一 owner」方向一致
  • 141cd63 的 worker/control 改动无交叠行,组合路径(engageCodexRpc ACK、spawn 重查、readiness 投影)逐一核过无交互回归

测试验证

  • pnpm build 绿。
  • 全量(BOTMUX_SCHEDULE_TIMEZONE=America/Los_Angeles):665/667 文件、10624 passed / 1 skipped;仅剩 v3-distillation×6 + session-discovery.smoke×2 两个已知宿主环境基线(后者上轮已在纯 master 复现)。无 PR 归因新增失败。

未阻塞的遗留(follow-up 即可)

上轮 ~25 条 minor 未在本轮处理(.retired-* 残留清扫、explicit_close_in_progress 双语义错误串、queued 输入拒整机 shutdown、restart-report 45s 窗口、v3 relay 端口优先级、restore 屏障挡 shutdown 路由、queuedActivationTail 上界、transferSession 绕锁、picker stdin 重入等),均为低概率/可观察项,建议挂 follow-up issue 逐步消化。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants