Skip to content

fix(worker): 避免慢启动误报消息未接收 - #1050

Open
swtxbling wants to merge 4 commits into
deepcoldy:masterfrom
swtxbling:fix/worker-startup-backpressure
Open

fix(worker): 避免慢启动误报消息未接收#1050
swtxbling wants to merge 4 commits into
deepcoldy:masterfrom
swtxbling:fix/worker-startup-backpressure

Conversation

@swtxbling

@swtxbling swtxbling commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

背景

ordinary IM 投递把两个阶段混在了一起:

  1. 父进程把消息写进 Worker IPC;
  2. Worker 领取消息,并提交到执行队列。

Worker 忙时,第二阶段的回执可能晚于 2 秒。旧逻辑会在同一 Worker 上重试,并最终提示“未接收,请重发”,但原消息仍可能稍后执行;用户重发会生成新的 turn_id,从而造成重复执行。

Closes #1018

改动

  • 独立跟踪 IPC transport callback 与 received / committed ACK。
  • 只有 IPC transport 未确认时,才沿用同一 Worker generation 内的有限重试。
  • IPC 已确认后不再重投;ACK 较慢时,提示消息仍在排队或尚未确认提交,并明确不要重发。
  • Worker 退出时区分冷启动失败、主动迁移或关闭、以及正常运行中的异常退出,避免重复或错误通知。
  • 删除中英文“请重发”诱导文案。
  • 补充 cold/warm、迟到 ACK、callback 竞态、transfer、退出结算和英文 locale 回归测试。

验证

  • npx --yes bun@1.4.0 run build
  • 5 个相关测试文件:156 passed
  • node_modules/.bin/tsc --noEmit
  • node_modules/.bin/tsc -p tsconfig.scripts.json --noEmit
  • 全量 unit:18,529 passed / 21 failed / 92 skipped
  • 同条件 clean master 对照:上述 21 个失败数量和原因完全一致,均为既有环境/平台失败
  • git diff --check

边界

本 PR 只修正投递状态语义和误导性重试,不新增父进程持久队列、全局冷启动并发限制、跨 Worker generation 自动重投,也不修改 Bun standalone 的 preload 传递;这些应作为独立问题评估。

已知残留窗口(R3 复审确认不阻断,作为 follow-up 记录):

  • commit 已被观测 → 之后才 suspend:commit ACK 一到,投递记录即被清除;若主动回收恰好落在 commit 之后、CLI 真正执行之前,已入队的 turn 随 CLI 销毁且无终态通知。常规流不踩(cap sweep 与手动 suspend 均过 lastScreenStatus === 'idle' 闸,正在产出的会话不会被回收),窗口只有 commit→flush 量级;堵它需要给「已 commit 未产出」设超时,会重新引入本 PR 要消灭的误报类别(真慢 vs 假挂死无法区分),故不在本 PR 处理。

@swtxbling
swtxbling requested a review from deepcoldy as a code owner August 27, 2026 18:44
@deepcoldy

Copy link
Copy Markdown
Owner

你好 @swtxbling 👋 感谢这个 PR!方向我们非常认可:「送达」和「执行」确实是两件事,旧逻辑把三个投递阶段混在一个 2 秒计时器里,冷启动慢时误报「请重发」进而导致重复执行,这个修复切中要害。CI 与本地构建/测试我们都跑过,全绿。

两位 reviewer 独立复审后,有 2 条合前建议(这是自动评审的初步意见,最终以维护者审阅为准):


🟠 建议 1:⏳ 通知发完后投递记录被删除,后续真实失败会静默丢弃(建议合前修复)

delayOrdinaryImDelivery() 第一行就调 clearOrdinaryImDelivery(record) 把 record 从 pendingOrdinaryImDeliveries 删除。但 rejectOrdinaryImDelivery / settleOrdinaryImDeliveriesForWorker / completeOrdinaryImDelivery 都是先 .get(key)、拿不到就 return。我们用独立探针测试复现了行为差异:

  • PROBE-1:receipt → 2.1s(⏳ 触发、record 已删)→ Worker 明确回 turn_input_rejected: cli_input_unavailablesendToPty 返回 false 的真实路径)→ 无重投、无任何失败通知。对照组(同样的 reject 在 ⏳ 之前到达)→ 正常重投 + 可见失败通知。
  • PROBE-3:⏳ 之后 worker 崩溃(exit 1、非 killed、会话 active)→ 同样静默;对照组(崩溃在 ⏳ 之前)→ worker_exited_after_receipt 可见失败通知。

用户停留在「请勿重发」,而这条消息永远不会执行。误报的代价是重复执行,但这种静默丢失同样违背本 PR 的初衷。

另外一层:兜底的 ordinaryTurnRecovery 只对 claude-code 生效(ordinaryTurnRecoveryEligible 硬判 sessionCliId === 'claude-code',两条 attach 路径都过这个门)。我们没找到其他 daemon 侧的网(worker 侧的 stuck detector 随 worker 一起死了)。所以除 claude-code 外的会话在 ⏳ 之后彻底失去跟踪。

还有个附带发现:bmx-recovery- turn 走 delayOrdinaryImDelivery 时也会被删 record,且在发任何用户通知之前就 return 了——连 ⏳ 都没有,比普通 turn 更静默(recovery_delivery_failed 的 attention 也永远到不了)。

建议:⏳ 只是一次「中途播报」,不应终结跟踪。发通知后保留 record(加 delayNotified 标记防重复播报),让后续的 reject / exit 仍能落到 failOrdinaryImDelivery

🟠 建议 2:lifecycleRetirement 两处新判据零测试覆盖,且它是承重的

我们做了反向对照:同样的 suspend 场景(suspendWorker() 走顺利路径只 send({type:'suspend'})、从不 kill(),Worker 自己 exit(0)、killed=false),如果有 lifecycleRetirement 判据 → 无误报;去掉该判据的语义(exit(0) 但未登记 retirement)→ 误报「无法确认消息是否已进入队列」——这正是本 PR 要消除的那类误报。判据是对的,但没有测试锁住它。MUT-A/C/D 三处变异测试都有测试拦住,唯独这里没有。建议补一条 suspend/retire 场景的断言。


关于 ⏳ 误报率(信息同步,不构成阻断):我们实测了线上 26 个 daemon 日志里 1076 次真实冷启动的 received→commit 间隔,>2000ms 的只有 0.5%,且都最终 ready 成功。另外我们核对了 worker 侧代码:init 的 commit ACK 在同一同步块里紧贴 ready 之前发出(同一有序 IPC 通道),所以用 ready 做时间锚得到的 0.5% 是上界,真实误报率只会更低。唯一「永不发 commit ACK」的路径是 codex-app + RPC fresh(线上暂无使用),其余 CLI(含 traex)都会命中 else if (msg.prompt) 兜底发送 commit。

影响面我们复核过:只动 ordinary-IM 投递跟踪 + i18n 文案,不改 Worker 侧协议;shouldTrackOrdinaryImDelivery 对 durable/meeting-driven/shared-adopt/VC 的排除与现有语义对齐。

小结:方向 🟢,建议按上面 2 点修改后合入。再次感谢!

@swtxbling

Copy link
Copy Markdown
Contributor Author

感谢复审,这两点已经在 24f63c7e 补上:

  1. delayOrdinaryImDelivery() 现在只发送一次中途通知,不再删除 delivery record;delayNotified 防止晚到 receipt 重新武装计时器后重复播报。后续 commit 会清理记录,reject 仍重试/失败,worker exit 仍给出最终通知。
  2. 新增直接调用 suspendWorker() 的生命周期测试,覆盖 worker 自行 exit(0)killed=false 的真实 retirement 路径,锁住 lifecycleRetirement 判据。

同时补了 delay 后 late commit、late reject、worker exit,以及「IPC enqueue 已提示后 receipt 晚到不得二次提示」的回归测试。

本地验证:

  • test/session-lifecycle-start.test.ts:147/147
  • 两套 TypeScript typecheck:通过
  • 完整 build:通过
  • git diff --check:通过

新的 GitHub CI 已由本次 push 触发,等它跑完后我再确认最终状态。

@swtxbling
swtxbling force-pushed the fix/worker-startup-backpressure branch from 24f63c7 to 6894611 Compare August 28, 2026 02:34
@swtxbling

Copy link
Copy Markdown
Contributor Author

补充确认:后续 CI 已全部通过(build 8m52s,bun-binary 1m11s)。当前分支可合并且工作区干净,等待维护者最终审阅。

@swtxbling

Copy link
Copy Markdown
Contributor Author

补充确认:最新一轮 CI 已全部通过:

  • build:完整 build、test、workflow-core:test 均成功
  • bun-binary:构建、编译、smoke 均成功

分支已同步最新 master,两条合前建议均已落实,现等待维护者最终 Review。

@deepcoldy

Copy link
Copy Markdown
Owner

你好 @swtxbling 👋 R2 复审完成 —— 两条建议都已落地,我们独立验证确认真修好了,方向 🟢。

先说结论:这轮已达可合状态,下面只剩 1 条 🟡 非阻断建议(可以顺手补,也可以作为 follow-up)。(这仍是自动评审的初步意见,最终以维护者审阅为准。)


✅ 建议 1:确认修好

clearOrdinaryImDeliveryclearOrdinaryImDeliveryTimer + delayNotified 标记,把 ⏳ 从「终结跟踪」降级成「中途播报」—— 正是我们建议的语义。

我们没有只看新增测试,而是把上一轮暴露缺陷的同一批探针原样重跑

场景 修前 修后
⏳ 之后 worker 崩溃 ❌ 只剩「请勿重发」,消息永不执行 ✅ 报出「无法确认…执行队列」
⏳ 之后收到 turn_input_rejected ❌ 无重投、无通知 ✅ 真重投,第二次拒绝后可见失败
bmx-recovery- turn 的 recovery attention {崩在⏳前: 1, 崩在⏳后: 0} {1, 1}

第三行是我们最在意的一条:bmx-recovery- 全仓唯一铸造点在 ordinary-turn-recovery.ts,而那个服务被 ordinaryTurnRecoveryEligible 硬判 claude-code。修前 ⏳ 一响就把这批会话最后一道兜底网拆掉了;现在(spy 真实的 requireOrdinaryTurnRecoveryAttention 验证)已经接回来。

「⏳ → reject → 重投 → 崩溃 → 终态失败通知」整条链路现在都有声了。

✅ 建议 2:零覆盖已补(一半)

新增的 suppresses ordinary delivery failure when suspendWorker retires the worker 真有牙:删掉 suppressDeliveryFailure 里的 || lifecycleRetirement → 🔴 1 failed(上一轮同样的变异是 147 全绿)。


🟡 唯一残留:另一处 lifecycleRetirement 仍零覆盖,且它可达

上轮提到的是两处判据。suppressDeliveryFailure 补上了,但 worker.on('exit')start_exited_early 门槛的 && !lifecycleRetirement 仍然没有测试锁住:

删掉 start_exited_early 门槛里的 && !lifecycleRetirement  → 147 全绿

它不是装饰。构造「冷启动带 prompt + 在 ready 之前 suspend」(suspendWorker 顺利路径只 send({type:'suspend'}) 从不 kill(),Worker 自行 exit(0)killed=falsepreReadyExit=true):

  • 判据在 → 无通知 ✅
  • 判据删掉 → 误报「worker 在就绪前退出」🔴(一次主动 suspend 被说成启动崩溃)

而这个 pre-ready 窗口是真能进 suspendWorker 的,机制是:

  1. screen_update 在 pre-ready 直接 break!startupState.ready && !workerHasInitialized(ds)),所以冷启动窗口内不写 lastScreenStatus
  2. forkWorker 从不重置 lastScreenStatus(全文件的赋值点里没有它);
  3. 于是崩溃后重 fork 的会话,会把上一代残留的 'idle' 带进 pre-ready 窗口 —— 而 idle-worker-sweeper 的候选过滤正是 .filter(ds => ds.lastScreenStatus === 'idle')live_worker_cap 一触发就会 suspend 一个还没 ready 的 worker。

建议:把新增的那条 suspend 测试扩一个 pre-ready 版本(ready 之前调 suspendWorker,断言不出现「就绪前退出」)。危害与建议 2 同型(都是误报),触发面更窄,我们认为不值得为它卡一轮

关于「⏳ 之后不设终局兜底超时」

我们注意到 ⏳ 播报后不再有任何定时器(记录进入停表挂起,等 worker 事件收尾),所以「Worker 进程活着、既不 commit 也不 reject 也不退出」这一种状态没有终局,用户会一直停在「请勿重发」。

我们核过之后认为这个取舍是合理的,记录在此供维护者参考:

  • 不泄漏:worker.on('exit') 无条件调用 settleOrdinaryImDeliveriesForWorker,记录生命周期被 worker 进程兜住;手动休眠/重启会触发 exit → 照常收到终态通知。
  • 这个状态不是本 PR 制造的:master 上同一个 wedged 冷启动约 4s 后提示「请重发」,但重发救不了 wedged 的 spawn(新消息只会排到同一个 init handler 后面)—— 那反而是错误建议;本 PR 的「请勿重发」在这个场景里是正确建议
  • 加超时的代价,正是重新引入本 PR 要消灭的那类误报(真的 spawn 慢 vs 假的挂死无法区分)。

如实保留一个边界:我们没有量化「进程活着且永久沉默」的线上发生率(我们从生产日志统计的 1076 次真实冷启动里,received→commit 最大 5.4s,零样本)。


顺带一个信息同步:我们统计了线上 26 个 daemon 的 1076 次真实冷启动,received→commit 间隔中位数 222ms、p99 934ms、超过 2s 的只有 0.5%(且都最终 ready 成功)。也就是说新增的 ⏳ 在正常冷启动上几乎不会触发,这个量级我们认为没问题。

感谢你这轮的修改,两条建议都按语义落地了,改得很干净 🙏

@swtxbling

Copy link
Copy Markdown
Contributor Author

本次 push(42135a76)在 R2 已验收的基础上,除补上那条 🟡 建议的 pre-ready suspend 测试外,还根据我们这边一轮独立对抗审核的发现,把 lifecycle retirement 的结算语义修正了一层。改动 5 个文件,+237/−5(源码 +45 行,其余为测试)。

审核发现的两层问题

  1. R2 验收的判据把「主动回收」一律静默结算——但 worker 被休眠/更换时,在途未 commit 的 tracked 消息随 CLI 销毁、无跨代重投接盘,用户完全无感。master 同路径至少还发一条(措辞不准的)提示;一律静默把可见失败降成了静默丢失,与本 PR「消灭静默丢失」的初衷相悖。
  2. 「结算时仍在途 ⇒ 必然未 commit」不可靠:commit ACK 是 fire-and-forget 裸 process.send,而 suspendWorker 先置空 ds.worker、替换 fork 先推进 generation——晚到的 ACK 会被 stale 门整体丢弃,已 commit 的 turn 在退出结算时仍显示 pending。据此断言「一定未执行、可重发」会诱导重复执行。

修法(两层)

  • stale 门放行退休 worker 自己的 commit ACK:新增 completeStaleWorkerOrdinaryImDelivery,按记录的 worker 对象身份结算该代的 delivery。receipt ACK 刻意不放行——它不具结算性,吞掉计时器反而会掐死原代的可见失败链(既有测试 ignores a stale worker ACK... 锁着这条不变量)。stale worker 不获得任何其它权威。
  • 退出结算时仍 pending 的记录,主动回收下发「诚实未确认」终态通知(新 key worker.input_retired_unconfirmed,zh/en):说明会话被主动休眠或更换、未能确认消息是否进入执行队列,请先查看会话记录、若未执行再重发。不做无法证明的「一定未执行」断言。transfer / close / 普通 kill 的静默语义不变;bmx-recovery- 等沿用既有路由。

测试与验证

  • 新增/改写 7 条:post-ready 未 commit → 未确认通知;已 commit → 静默;late commit(suspend 后 / 替换 fork 后到达)→ 静默 ×2;pre-ready 冷启动 init(含 preload receipt 形态)→ 恰一条未确认通知且不误报「就绪前退出」(锁住 R2 那条 🟡 的 start_exited_early 门槛判据);en locale。
  • usage-refresh-timer-wiring 的 source-lock 从固定 3000 字节窗口改为语义截取整个 exit handler,并把 clearUsageRefreshTimer 锚定在 ds.worker === worker 分支内(handler 增长会把锚点推出固定窗口;整段 contain 又会对「无条件清理」假绿)。
  • 变异验证:四处判据/分支分别单独删除(start 门槛、suppress 判据、未确认通知分支、stale commit 结算),均被预期测试精准拦红(1/2/3/2 条);「clear 移到 handler 末尾」的变异也被加固后的 source-lock 拦红。
  • 本地门禁:生命周期文件 152/152、关联 14 文件 822 passed、两套 tsc、bun@1.4.0 run buildgit diff --check 全绿;全量 unit 与 R2 head 对照,除本次改动外失败集逐条一致(均为本机环境既有失败)。

CI 由本次 push 触发,跑完后我再确认最终状态。

@swtxbling

Copy link
Copy Markdown
Contributor Author

补充确认:本次 push(42135a76)的 CI 已全部通过(build / bun-binary / bun-binary-musl 三项均 pass),分支可合并且工作区干净,等待维护者最终审阅。

@deepcoldy

Copy link
Copy Markdown
Owner

你好 @swtxbling 👋 R3 复审完成 —— 这轮我们两个 reviewer 都是 0 阻断,而且要先说一句:

你的独立对抗审核推翻了我们上一轮的一个判断,你是对的。 我们 R2 说「lifecycleRetirement 判据承重,删掉它一次正常 suspend 就会误报」—— 我们验的是「删掉判据 → 出现通知」,但从没验那条通知本身是真是假。你查下去发现在途消息是真的丢了,那条通知本该发。我们把一条真实的可见失败当成误报给压掉了,这是我们方法上的漏洞,谢谢你顶回来。

(以下仍是自动评审的初步意见,最终以维护者审阅为准。)


我们独立复核的结论

1. 「suspend 时在途 tracked 消息无跨代重投」——证实。 我们两侧分别做了实测与读码,把每条候选重投路径都堵死了:

候选路径 结论
worker 内存队列 destroySession() + process.exit(0)pendingMessages 纯内存无落盘
ordinaryTurnRecovery(claude-code) onTerminal 硬性要求带匹配 turnId 的终态消息;suspend 不发 terminal,state 永停 'running',backoff timer 只在 retryable-failed 分支 arm ⟹ 网在,但接不住「没跑起来就被回收」的 turn
restartCoordinator.failSession 只标记 attempt failed,不带 turn 内容、不触发 fork
queuedActivation journal / repark 只覆盖 durable admission 流(需 queuedActivationToken
Lark 侧 事件已 claim + dedup,不会二次投递

行为实测也一致:同一 turn 全程只被发送 1 次、never redelivered。所以这条通知是必要且诚实的。

2. 第二层(stale commit ACK)我们认为是这个增量里最有价值的一点。 「结算时仍 pending ⟹ 必然未 commit」确实不成立(fire-and-forget 裸 process.send + suspend 先置空 ds.worker),而如果据此断言「一定未执行、可安全重发」,就会诱导重复执行 —— 正是本 PR 从头要消灭的东西。我们重点验了安全性:

  • 构造「gen1 suspend → gen2 接手同一个 turnId → gen1 的晚到 commit ACK 抵达」→ gen2 自己的跟踪未被吞掉(它的通知照常发出)。record.worker !== worker 的对象身份栅栏跨不过代,且只 clearOrdinaryImDelivery、不碰任何 durable state ✅
  • receipt ACK 刻意不放行的理由成立:stale receipt 无结算性,吞掉它会顺手清掉原代的失败计时器、掐死可见失败链。

3. 四处逻辑的反向变异——各自单删都被精准拦红,属实(我们两侧独立跑,结果一致):

变异 结果
start_exited_early 门槛的 && !lifecycleRetirement 🔴
suppressDeliveryFailure|| lifecycleRetirement 🔴
retiredBeforeCommit 通知分支 🔴
删 stale commit 结算路径 🔴

第一行正是我们 R2 报的那处零覆盖,现在真被锁住了 —— 那条 🟡 已闭合。 另外 bmx-recovery- turn 在 retirement 时现在会走 recovery_delivery_failed attention(R2 是静默清除),我们 R1 报的「recovery 兜底网被拆」在这条路径上也闭合了。

4. usage-refresh 的 source-lock 改动是加强、不是放宽。 我们本来怀疑改成语义截取是为了让门禁别拦自己,实测 clearUsageRefreshTimer 在 exit handler 内的偏移是 3224 —— 确实被 handler 增长挤出了旧的 3000 字节窗口,你的理由是真的。而新版还多加了两道(把 clear 钉在 dead-generation 分支内的位置区间 + 要求整个 handler 只出现一次),两个方向的变异都有牙:移走分支内的 clear → 🔴;在 handler 尾部加第二个无条件 clear → 🔴。


🟡 两条非阻断的记录(都不必卡这轮)

① commit message 里 master 那半句不准。 你写「master 同路径至少还发一条(措辞不准的)失败提示」,我们实测 master 是 0 条:把 master 的 worker-pool.ts + i18n 放到同一套测试替身下跑同一场景 → notices: 0。原因是 master 的 abandonOrdinaryImDeliveriesForWorker 在 exit handler 一进来就无条件静默清除cc5085b62:6517,调用点 12751),根本到不了任何通知分支。

准确表述其实对你更有利:不是「恢复一条烂提示」,而是**「master 在这条路径上一直是静默丢失的,本 PR 第一次让它有声」**。建议 squash 时把这半句改准即可。

② 「commit 已被观测 → 之后才 suspend」这个子窗口仍会静默丢 turn(我们实测确认:commit-then-suspend → notices: 0;对照组「未 commit + suspend」→ 正常通知)。commit ACK 一到 record 就被清,若 suspend 落在 commit 之后、CLI 真正执行之前,已入队的 turn 随 CLI 一起被杀,用户没有任何终态。

我们判不阻断,理由是它与你已经做过的取舍同构:

  • 常规流不踩:cap sweep 与 deferred 手动 suspend 都过 lastScreenStatus === 'idle' 这道闸,正在产出的会话不会被回收;
  • 窗口只有 commit→flush 的量级;
  • 要堵它就得给「已 commit 未产出」设超时 —— 那正是你刚拒绝过的误报类别(真的慢 vs 假的挂死无法区分)。

所以这条只是让你知道边界存在,作为 follow-up 记录即可。


小结:方向 🟢,R2 的 🟡 已闭合,本轮 0 阻断。 这个增量的质量比前两轮更高 —— 推翻了我们一个判断、补了一层我们没想到的 ACK 竞态、四处逻辑各自有牙、连 source-lock 都是加强的。辛苦了 🙏

独立对抗审核发现两层问题:
1. lifecycleRetirement 一律静默结算会吞掉真实丢失--worker 被主动
   休眠/更换时,在途的 tracked 消息随 CLI 销毁,没有跨代重投接盘,
   用户完全无感。master 在这条路径上本就一直是静默丢失(exit
   handler 一进来就无条件清除投递记录,到不了任何通知分支),本次
   改动让这类丢失第一次有声。
2. "结算时仍在途 => 必然未 commit"不可靠:worker 的 commit ACK 是
   fire-and-forget 裸 process.send,而 suspend 先置空 ds.worker、
   替换 fork 先推进 generation,晚到的 ACK 会被 stale 门整体丢弃,
   已 commit 的 turn 在退出结算时仍显示 pending,断言"一定未执行、
   可安全重发"会诱导重复执行。

修法(两层):
- stale 门放行退休 worker 自己的 commit ACK:按记录的 worker 对象
  身份结算该代的 ordinary delivery(receipt ACK 刻意不放行--它不
  具备结算性,吞掉计时器反而会掐死原代的可见失败链;stale worker
  不获得任何其它权威)
- 退出结算时仍 pending 的记录,主动回收下发诚实的"未确认"通知:
  会话被主动休眠或更换,未能确认消息是否进入执行队列,请先查看
  会话记录、若未执行再重发(不做无法证明的"一定未执行"断言);
  新 i18n key worker.input_retired_unconfirmed(zh/en)
- transfer/close/普通 kill 的静默结算语义不变;bmx-recovery-/
  meeting-driven/silent-scheduled 沿用 failOrdinaryImDelivery 既有路由

测试:
- post-ready 未 commit + suspend -> 一条未确认通知(改原静默断言)
- 已 commit 后 suspend -> 静默;late commit ACK(suspend 后/替换
  fork 后到达) -> 静默 x2(锁 stale commit 结算路径)
- pre-ready 冷启动 init(含 preload receipt)+ suspend -> 恰一条
  未确认通知,且非"就绪前退出"误报(锁 start_exited_early 门槛的
  && !lifecycleRetirement,此前零覆盖)
- en locale 变体
- usage-refresh source-lock 从固定 3000 字节窗口改为语义截取整个
  exit handler(handler 增长会把锚点推出固定窗口)

变异验证(四处逻辑分别单独删除,均有测试精准拦红):
start 门槛判据 / suppress 判据 / retiredBeforeCommit 通知分支 /
stale commit 结算路径。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@swtxbling
swtxbling force-pushed the fix/worker-startup-backpressure branch from 42135a7 to 4d90855 Compare August 28, 2026 11:17
@swtxbling

Copy link
Copy Markdown
Contributor Author

R3 的两条 🟡 已处理(本次 push 4d908556 仅改 commit message,代码 tree 与 R3 验收的 42135a76 逐字一致,tree hash 均为 3f7928a19d3b):

  1. commit message 已改准:删去「master 同路径至少还发一条(措辞不准的)失败提示」的错误表述,改为「master 在这条路径上本就一直是静默丢失(exit handler 一进来就无条件清除投递记录,到不了任何通知分支),本次改动让这类丢失第一次有声」。改前我们也独立复核了 master 的 abandonOrdinaryImDeliveriesForWorker(exit handler 首行调用、对匹配 worker 的记录无条件 clearOrdinaryImDelivery),与你们的实测结论一致。
  2. commit-then-suspend 残留窗口已按「follow-up 记录」落进 PR 描述的「边界」节,含不处理的理由(常规流被 lastScreenStatus === 'idle' 闸挡住、窗口只有 commit→flush 量级、堵它需重新引入本 PR 要消灭的误报类别)。

CI 由本次 push 重新触发,跑完后我再确认最终状态。

@swtxbling

Copy link
Copy Markdown
Contributor Author

补充确认:本次 push(4d908556,仅 amend commit message,代码 tree 与 42135a76 一致)的 CI 已全部通过:build 7m32s、bun-binary 1m5s、bun-binary-musl 1m34s。分支可合并,等待维护者最终审阅。

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.

fix(worker-pool): ORDINARY_IM_RECEIPT_TIMEOUT_MS 从入队起算,冷启 worker 时误报「请重发」而消息其实已执行

2 participants