Filed unassigned by the domain:services execution seat (session session_01APWX2AwT3a4xDcjPCe8bk4)。⛔ 不认领,定级归 triage。
⚠️先说清楚:锁本身是对的。 它保护的是共享构建产物,并发无锁会互相踩踏。本卡记的不是「不该有锁」,而是三次实测显示争用已越过「慢一点」的边界,产生了两种与吞吐无关的后果。
为什么由 PM 席记而不是 dev
每个 dev 只经历自己那一次争用,在它的视角里那是一次运气不好。只有派发席能看到它是系统性的——同一班次、三个互不相干的卡、三次同型。这是单个 dev 结构上看不见的东西,所以由这里记。
三次实测(2026-08-23,同一班次)
| # | 卡 | 形态 |
|---|
| 1 | #11076 / PR #11322 | 两次完整 540 秒排队超时(exit 99 = 从未获取),消融才拿到槽位 |
| 2 | #11111 / PR #11323 | 五次获取尝试,三次 exit 99;⚠️一次前台调用在约 10 分钟容器上限被 SIGTERM,当时 check:type-check-debt 跑到一半 |
| 3 | #10188(本轮) | 测试+typecheck 调用排在锁队列中,agent 就此结束回合,工作已就绪但未完成 |
同时被并发持有者观察到的还有:#10920 的 debt gate 持锁 511 秒、#10950、#11076 —— 即持锁时长本身是分钟级的,不是秒级。
⚠️ 两种非吞吐后果,才是本卡的重点
① 容器上限会杀掉持锁中的 gate(实例 2)。
一个前台 gate 调用在约 10 分钟上限被 SIGTERM,而它当时正处于 --re-measure 中途。那一次没有丢测量、也没有留下变异的工作树,唯一的原因是该 dev 自己写了 trap restore EXIT INT TERM。
⇒ 纪律救了它,而不是机制。 一个没写 trap 的消融在同样的时序下会留下一棵被变异的树,而那棵树上的下一次 gate 运行会给出一个看起来正常的错误结论。
② agent 会在排队中结束回合(实例 3)。
工作全部就绪(gates 已绿于 04323949c1、PR 正文已拟、只差测试与 typecheck 的判定),但回合结束在等待里。⇒ 需要外部推一把才能继续;若派发席没注意到,这张卡会以「已就绪但永不完成」的形态静置——这与 #10792 上今天已经修过的「半状态」是同一类隐形失败。
与「席位并发」的直接关系
本班的并发是被显式提高的(维护者当场:「任务很多,并发加到5」)。⚠️锁的争用与席位并发是超线性的——每张卡都要在其 gate union 里跑多次持锁调用,而其中最重的一次(check:type-check-debt --re-measure,需要整个工作区闭包构建)本身就是分钟级。
⇒ 这不是「以后可能会有的问题」。它在本班的并发度下已经在发生,而并发度是刚被调上去的。
可能的方向(未测量成本,⛔ 不由执行席自选)
- 提高排队超时,或让排队等待不计入容器前台上限 —— 直接消除后果 ①,不解决根因。
- 把
check:type-check-debt --re-measure 的闭包构建结果做成跨 worktree 可共享的产物,使多数持锁调用退化为读 —— 攻击的是持锁时长而非排队策略,可能收益最大,成本未知。 - 锁粒度细化(按包/按 gate 族而非全局),让互不相干的卡不互相排队。
- 最小口径改动:在
os-verify-lock.sh 的 exit 99 文案里写明「这是未运行,不是失败,也不是通过」,并建议 trap 模式。零成本,不解决问题,但让下一个 dev 不把 exit 99 读成一次失败的运行——本班三个 dev 都读对了,那是纪律好,不是文案好。
本班已被这条纪律挡住的失败
三个 dev 全部没有把 PREREQUISITE NOT MET 或 exit 99 记成通过:一个建好闭包重跑、一个等锁重试、一个声明为收窄并说明理由。真正的失败模式是把「没跑成」写成「过了」,三个都没犯。 记在这里,是因为它说明当前的安全边际来自 agent 纪律;而纪律是最不该被当作机制依赖的东西。
Refs:#11322 / #11076(实例 1)· #11323 / #11111(实例 2)· #10188(实例 3)· #11356(同班次另一条 gate 覆盖缺口)
Filed unassigned by the
domain:servicesexecution seat (sessionsession_01APWX2AwT3a4xDcjPCe8bk4)。⛔ 不认领,定级归 triage。为什么由 PM 席记而不是 dev
每个 dev 只经历自己那一次争用,在它的视角里那是一次运气不好。只有派发席能看到它是系统性的——同一班次、三个互不相干的卡、三次同型。这是单个 dev 结构上看不见的东西,所以由这里记。
三次实测(2026-08-23,同一班次)
check:type-check-debt跑到一半同时被并发持有者观察到的还有:#10920 的 debt gate 持锁 511 秒、#10950、#11076 —— 即持锁时长本身是分钟级的,不是秒级。
① 容器上限会杀掉持锁中的 gate(实例 2)。
一个前台 gate 调用在约 10 分钟上限被 SIGTERM,而它当时正处于
--re-measure中途。那一次没有丢测量、也没有留下变异的工作树,唯一的原因是该 dev 自己写了trap restore EXIT INT TERM。⇒ 纪律救了它,而不是机制。 一个没写 trap 的消融在同样的时序下会留下一棵被变异的树,而那棵树上的下一次 gate 运行会给出一个看起来正常的错误结论。
② agent 会在排队中结束回合(实例 3)。
工作全部就绪(gates 已绿于
04323949c1、PR 正文已拟、只差测试与 typecheck 的判定),但回合结束在等待里。⇒ 需要外部推一把才能继续;若派发席没注意到,这张卡会以「已就绪但永不完成」的形态静置——这与 #10792 上今天已经修过的「半状态」是同一类隐形失败。与「席位并发」的直接关系
本班的并发是被显式提高的(维护者当场:「任务很多,并发加到5」)。⚠️ 锁的争用与席位并发是超线性的——每张卡都要在其 gate union 里跑多次持锁调用,而其中最重的一次(
check:type-check-debt --re-measure,需要整个工作区闭包构建)本身就是分钟级。⇒ 这不是「以后可能会有的问题」。它在本班的并发度下已经在发生,而并发度是刚被调上去的。
可能的方向(未测量成本,⛔ 不由执行席自选)
check:type-check-debt --re-measure的闭包构建结果做成跨 worktree 可共享的产物,使多数持锁调用退化为读 —— 攻击的是持锁时长而非排队策略,可能收益最大,成本未知。os-verify-lock.sh的 exit 99 文案里写明「这是未运行,不是失败,也不是通过」,并建议trap模式。零成本,不解决问题,但让下一个 dev 不把 exit 99 读成一次失败的运行——本班三个 dev 都读对了,那是纪律好,不是文案好。本班已被这条纪律挡住的失败
三个 dev 全部没有把
PREREQUISITE NOT MET或 exit 99 记成通过:一个建好闭包重跑、一个等锁重试、一个声明为收窄并说明理由。真正的失败模式是把「没跑成」写成「过了」,三个都没犯。 记在这里,是因为它说明当前的安全边际来自 agent 纪律;而纪律是最不该被当作机制依赖的东西。Refs:#11322 / #11076(实例 1)· #11323 / #11111(实例 2)· #10188(实例 3)· #11356(同班次另一条 gate 覆盖缺口)