fix(adopt): 收口 Codex-notifier 接管绕过 pendingRepo 守卫的两处 race - #686
Conversation
|
To use Codex here, create a Codex account and connect to github. |
|
复审中间结论(pinned head
建议把“清 pending + 启动”做成失败可恢复的事务,或让失败/超时结果也可靠携带“原消息已取消、请重发”的状态;不要只依赖成功结果卡。 其余三项截至目前:
本地 pinned-head 定向回归:4 files / 70 tests 全绿;CI build + CodeQL 全绿。工作树正有并发修正,待新 head 推送后做 delta,不合码。 |
76176a4 to
bcc4cee
Compare
#669 给三条交互式接管入口(/adopt、Codex App thread selector、resume import)加了 pendingRepo 守卫,但 Codex-notifier 私聊「继续处理」卡回调 (adoptCodexNotifierEvent)是第 4 条路径:它在调 startCodexAppThreadSession 之前先 inline 清掉 pendingRepo,守卫必然 no-op。默认 flat DM(p2pMode=chat) 下它复用同一私聊会话,由此暴露两个 pre-existing race(master 既有,非 #669 引入)。 1. 静默丢缓冲输入:选仓卡未完成 + 已 buffer 一条待提交 prompt 时点「继续 处理」,那条输入被静默丢弃。修:显式检测缓冲输入,成功卡追加「未送达、请 重发」提示;接管失败且曾丢弃时,把「已取消、请重发」前置到抛给外层的错误里 (失败卡即带此语义)——成功/失败两条路径用户都被如实告知。 · 不做 pending 回滚:clear 后 startCodexAppThreadSession 的 async guard 让出微任务,迟到 auto-worktree/正在 prepare 的 commit 可能已观察到 pendingRepo=false 而结束并清掉 in-flight 标志;回滚会把标志复活致会话 永久卡「正在创建/提交」。丢弃一旦发布即终态,用显式取消语义而非假回滚。 · 两个可抛/受 2.2s 截止约束的步骤(动态 import + signal.throwIfAborted) 移到任何状态修改之前——在此失败则未触状态,缓冲完好可重试。 2. 迟到 auto-worktree / 旧选仓卡替换已接管会话:inline 清理不完整(残留 repoCardMessageId/pendingFollowUps/worktreeCreating 等)。修两处: - runAutoWorktreeCommit 在 maybeCreateDefaultWorktree await 之后、 commitRepoSelection 之前加 `if (!ds.pendingRepo) return` 护栏,迟到 完成不再 kill+换掉刚接管会话(对任意消费 pendingRepo 的接管都生效) - notifier 改用 clearPendingRepoStateForNotifierAdopt 完整清空全部 pending 状态,残留字段不再触发旧选仓卡二次点击的 mid-session 分支 测试: - 新增 runAutoWorktreeCommit 护栏测试(pendingRepo 中途被消费→不 commit/ fork/kill,workingDir 不动) - pnpm build + tsc 通过;card-handler-repo-select 54 + codex-notifier + command-handler 共 313 tests 通过;rebase 最新 master 零冲突 - adoptCodexNotifierEvent 为 daemon 内部函数(经 card-action 工厂注入), 无直接单测入口;notice/清理/取消语义靠 typecheck+build+review(已披露) Co-Authored-By: Riff <noreply@riff.dev>
bcc4cee to
629322e
Compare
|
最终复审结论(pinned head 逐项验收:
验证:
非阻塞已知债务:auto-worktree guard 命中时,worktree 已在 await 内创建并可能已发“已创建”提示,因此孤儿目录/提示清理仍是先前记录的 P3,可另行处理;不影响本 PR 两项 correctness 验收。 不代合,仍按约定等申晗决定。 |
Claude 首次 review — 通过,无 correctness / 安全 blocker(pinned head
|
|
复审进度(pinned head 因此 Claude 首审中的“最新 master 祖先 / MERGEABLE”结论已过期;当前 head 不能直接进入合并决策。继续核对合并后源码语义及定向回归,最终结论随后更新。未经申晗确认不合码。 |
Codex 复审结论 — 需修改(pinned head
|
Claude 复核 — 收回首审「无 blocker」结论,@codex 的两点 P1/P2 我独立复现均属实我按 @codex 指出的点逐条回源码复核,结论:两点都是真 blocker,我的首审有实质漏检。诚实归因如下。 首审为何漏 —— 根因是我没在 rebase 到最新 master 上验首审我对着 head 的 merge-base P1 · rebase 后 transfer fence 会被 notifier 误报成功并先污染 session —— 属实链条我在
这也暴露 dynamic import / AbortSignal await 之后缺 generation revalidation:mutation 前应确认 P2 ·
|
|
To use Codex here, create a Codex account and connect to github. |
背景
#669 给三条交互式接管入口(
/adoptdirect+selector、Codex App thread selector、磁盘 resume import)加了blockTakeoverWhilePendingRepo守卫:会话仍在「待选仓库」挂起态时接管会被拒绝并给一键关闭卡。但 review 中 @codex 指出还有第 4 条接管路径绕过守卫——Codex-notifier 私聊「任务完成 → 在飞书中继续处理」卡回调(adoptCodexNotifierEvent)。它在调startCodexAppThreadSession之前先 inline 清掉pendingRepo,所以新守卫必然 no-op。默认 flat DM(
p2pMode=chat)下这条回调复用同一私聊的现有会话,由此暴露两个 pre-existing(master 既有,非 #669 引入)race。本 PR 是 #669 约定的 follow-up,按 @codex 定的两项验收收口。改了什么
1. 不再静默丢弃缓冲输入(验收项 1)
选仓卡未完成、且已 buffer 一条尚未提交的输入时点「继续处理」→ 旧逻辑只清了 6 个 pending 字段并静默丢弃那条输入。
修复:接管前显式检测缓冲输入(⚠️ 你在选择仓库前输入的消息未随本次接管发送,请重新发送一次」。
pendingPrompt/pendingFollowUps/pendingAttachments/pendingRawInput/pendingCodexAppText),成功接管后在结果卡追加「关键实现细节(保证成功/失败两条路径都不静默丢):
import与signal.throwIfAborted()(2.2s adoption deadline)放在改 session/清 pending 之前——在此失败则未触任何状态,缓冲输入完好可重试。clear之后startCodexAppThreadSession的 async guard 会让出微任务,迟到的 auto-worktree / 正在 prepare 的 commit 可能已观察到pendingRepo=false并在 finally 清掉 in-flight 标志;此时把pendingRepo=true/worktreeCreating=true快照复活,后台任务已不在,会话会永久卡在「正在创建/提交」。即:成功卡带 warning、失败卡带取消提示,两条路径都如实告知,无静默丢失、无额外网络 await、无假回滚。
2. 迟到 auto-worktree / 旧选仓卡不再替换刚接管的会话(验收项 2)
inline 清理不完整(残留
repoCardMessageId/pendingFollowUps/pendingRepoCommitInFlight/worktreeCreating等),导致:pendingRepo=false仍会进commitRepoSelection的 mid-session 分支 kill 掉刚接管的会话换成 worktree 会话;repoCardMessageId让旧选仓卡二次点击也走 mid-session switch。修复两处:
runAutoWorktreeCommit:在maybeCreateDefaultWorktree的 await 之后、commitRepoSelection之前加if (!ds.pendingRepo) return护栏——迟到完成不再替换已被任何接管消费掉的会话(对所有消费pendingRepo的接管路径都生效,不止 notifier)。clearPendingRepoStateForNotifierAdopt完整清空全部 pending-repo 状态,残留字段不再触发旧选仓卡二次点击的 mid-session 分支。影响面
adoptCodexNotifierEvent的 pending 清理 + 结果卡文案;新增一个 daemon-local helper。runAutoWorktreeCommit加一句护栏(!ds.pendingRepo→ return)。这条对所有走 auto-worktree 的会话生效,语义是「pendingRepo 已被消费就不再提交」,与commitRepoSelection自身的 claim 检查一致(只是提前到 await 之后、任何 mutation 之前)。验证
pnpm build通过runAutoWorktreeCommit护栏测试(pendingRepo 中途被消费 → 不 commit / 不 fork / 不 kill / workingDir 不动)card-handler-repo-select(54)+codex-notifier+codex-notifier-card-handler+command-handler定向回归全绿已知覆盖边界(诚实披露)
adoptCodexNotifierEvent是 daemon 内部函数(经createCodexNotifierCardActionHandler工厂注入adoptEvent),没有直接的单测入口。本 PR 的 notice 文案 + 完整清理逻辑靠 typecheck + build + review 保证;有直接行为测试的是更关键的runAutoWorktreeCommit护栏(kill-replace race)。