Repository navigation
Conversation
Browser-lane flake analysis and CI retriggerThe What failed. Four of the five samples measured 511-765 ms. One sample reached 1345 ms, Why it is not this branch. The same lane, in the same window, on two other
The failing sample is outside the distribution the same lane produces. The Why the budget was not touched. The 1 s budget is frozen, and this entry Why One condition outside this PR. The six |
Browser-lane update: the same scenario failed a second time, on a fresh runnerThe retriggered head ( The measurements
The frozen 1 s budget carries roughly 30% headroom on a healthy runner Why this branch's diff is not the causeThe diff is Python-only ( The shard-2 job was also measurably slower end to end on both runs of this What was done, and what was notThe head was retriggered again ( The observation worth the maintainer's attention is a shared-lane property |
songoow
left a comment
There was a problem hiding this comment.
Reviewer: model_agent | GPT-6 Astra Pro | OpenAI | self_reported
REQUEST_CHANGES — 1 项 P2:请修正启动超时的排他性因果解释。 当前实现能保留迟到或已经发布的精确诊断;没有从本轮检查中发现新的 runtime 正确性缺陷。审阅 head ef93a4591e2558a5e7dffe39440ff7ff870efdc9,merge-base 0ad08b15dd7331051c93566075561a390a9cd7e2;已完整阅读6个改动文件、TS发布端和Python/CLI消费者。该head的parent是bf376820,两者与9091a785的tree完全相同(92e9e640…);两个后续commit均只重触发CI。本轮在当前ef93隔离worktree重新执行了2个新增测试、deadline反例和文档检查;此前完整矩阵保留实际bf出处。
动机 / Motivation
受影响的是通过CLI、Chat或其他Python入口启动共享Effect runtime、需要据错误码选择修复动作的操作者。
原先父进程跨过ready截止点时,已发布的mutation_lock_timeout也可能丢失;当前head在有界grace内读取locator或安全错误信封,真实合成反例从通用超时变为精确错误。
Linux真实runtime的迟到错误和有界无输出路径成立;额外等待是明确行为变化,原Windows故障的唯一根因仍不能由一个timeout码推出。
本PR不改变mutation-lock业务语义、任务结算、重试授权或模型成绩;也不要求它证明整个Windows CI、安装矩阵或父roadmap已完成。
改动思路 / Approach
错误码继续由TS发布,Python只负责读取、等待和OS生命周期;本次无需建立第二套故障分类器。 保留现有startup owner中的局部grace与安全解析,修正双语ledger和相关排他性注释;不扩展成通用retry或Windows总资格项目。
这里有两个不同边界:其一,child已经发布并退出,parent在最后一次sleep后跨过deadline而没有补读;其二,child确实需要更长时间才发布。末尾补读能修复前者,grace进一步覆盖后者。新代码实际覆盖了二者,功能收益无需依赖“原Windows失败必定属于第二种”的推断。
等待成本也应直说:原ready阶段15秒之后,现在可以再等15秒。TS mutation-lock常量未变,但默认启动等待阶段确实增加;启动锁、Node probe和外层既有retry仍各有其成本,所以这不是整个调用的绝对30秒上限。正常ready调用仍立即返回。
具体改动 / Changes
关键代码讲解
_published_startup_failure把旧exited分支的安全解析提取出来;它仍只接受原typed schema/code/message,保留host-permission特殊异常,不把任意Node堆栈或stderr文本当作业务诊断。_await_startup_publication在deadline前验证locator,再检查进程和typed envelope;可接受窗口内晚到的locator,也可在child仍活着时转交已经公布的精确错误。无信封而退出仍返回runtime_exited_before_ready;没有任何publication则返回调用方完成最终补读和终止。_start_runtime保留原启动锁和Popen owner,将原ready之后分支接入grace;effect_runtime_request.retry_safe仍拥有重试决定,新增helper不授权交易重放。foreign mutation lock也不因等待结束而删除。
总计464新增、13删除,包含局部生产等待/解析、115行测试、259行双语ledger和两个STATUS机械计数。两个新测试分别使用真实TS runtime与live mutation lock验证迟到精确码,以及受控时钟验证无任何输出时仍有界并terminate;测试并非只重复helper内部判断。
既有规格采用docs/architecture/rfcs/ledger/loopx-overall-roadmap-v0/2026-10-04-s2-s12-startup-diagnostic.md,修订0ad08b15dd7331051c93566075561a390a9cd7e2。本次映射标签exact-startup-code、bounded-safe-envelope、foreign-occupant-preserved分别要求精确码、安全有界信封和foreign占用保护;它们不是从新ledger反向定义的验收。当前功能证据覆盖这些要求,但不能据此补写原Windows现场的唯一因果。
对主干的风险 / Risks
P2,新增英文ledger第20–24行及对应中文说明:runtime_startup_timeout不能证明child在15秒内没有退出。 base的ready循环结束后只在poll() is None时执行terminate(),随后无条件抛出generic timeout;即使最后的poll()已经是2,也不会再读stderr。这使“最后一次观察后child退出、parent恢复时已跨过deadline”成为真实可达分支。
我用生产_start_runtime和真实Python child验证这个边界:child先写合法的mutation_lock_timeout信封并退出2;仅parent的时钟/恢复时点受控。相同脚本得到:
| 可观察量 | 原base实现 | 当前head |
|---|---|---|
| child最终退出码 | 2 | 2 |
| 已公布的诊断 | mutation_lock_timeout | mutation_lock_timeout |
| 返回给调用方的code | runtime_startup_timeout | mutation_lock_timeout |
| 生产capture读取次数 | 0 | 1 |
| 自己的startup lock | 已清除 | 已清除 |
因此,成功样本的约5.8秒耗时和历史generic timeout都不能排除parent错过最后观测;这不是原Windows故障现场的重现,也不证明它实际走了哪一支。请在双语ledger、PR说明及相关排他性注释中保留这个不确定性,分别说明两种边界及grace的实际等待成本。无需为了更正文档另开一个完整历史调查项目,也无需撤销已经验证有效的诊断修复。
当前ef93上重新运行的两个新增测试为 2通过 / 6.20秒;上述deadline反例也重新得到精确码、exit2和startup lock清除,git diff --check、RFC索引、双语差分和docs-governance均通过。同tree的bf376上先前5文件完整矩阵是303通过、1个既有Node取消probe失败;相同最小取消用例在不可变main也失败,bf单跑则通过。该测试与node_probe未改、取消发生在runtime spawn之前,因此不归因于新grace,也不宣称其稳定性已解决。没有把bf的执行日志改写成ef93的全矩阵通过。
我阅读了作者对9091/bf两次浏览器render-budget失败和两个空提交重触发的说明;本PR没有dashboard/browser源码diff。旧run的聚合结果与当前pending CI不是本轮native Windows或浏览器资格证据,不能用来补足未执行项。没有运行整套pytest、真实Windows、安装、PostgreSQL或模型实验。
语义与 CI 对齐
本次复用原typed错误词汇,未增加权限、任务义务或重试owner。新head的诊断行为与既有合同一致;阻塞点是新增说明把可观察结果扩张成了已证明的唯一原因。需要调整的是证据叙述,并保留可覆盖deadline竞争的回归;不应通过放松错误码断言来消除争议。
我的整体评价 / Verdict
小范围启动修复方向合理,真实迟到错误与最终无输出都已有有用的独立验证。精确诊断对操作者和长任务恢复有正向价值,新增等待成本也可清楚说明。但仓库ledger应能指导下一次排障,当前排他性推断会让维护者跳过一个已经复现的分支,因此建议修正后再接受。原Windows失败原因保持未确定;这不等于要求本PR承担全平台或父roadmap总验收。本次检查的 effect_runtime.py、内部启动测试与两份RFC ledger 不改变已渲染的产品页面;无需用截图证明内部行为。 未进行产品页面视觉检查,因为本次没有页面改动。
English verdict: REQUEST_CHANGES — ef93a4591e2558a5e7dffe39440ff7ff870efdc9 has one P2 documentation defect: a generic startup timeout does not establish that the child was still running at the ready deadline. A real-child, controlled-parent boundary probe disproves that exclusive inference while confirming the current implementation preserves the published typed error. Targeted Linux behavior is verified within the stated limits; native Windows and full CI qualification are not claimed.
The shard-2 pattern is reproduced on another branchThe third attempt failed the same way (814, 792, 612, 607, 1213 ms). The That shape is not this branch's. The run of
A loaded shard-2 runner ends the shard early — the failing assertion aborts For scale, the same scenario on a developer machine measures 128, 134, 156, The head has been retriggered again ( |
Review response: the attribution is corrected, and the missed-exit case is now pinnedThe finding is accepted, and the correction is on the branch ( What was wrong. The ledger and the PR body stated that What changed.
New test for the missed exit. Unchanged. The implementation itself is not changed by this response: the |
…y budget A managed runtime publishes its typed startup diagnostic when it gives up, so the host could only read the envelope after the child had already exited. On a loaded runner the child was still running when the ready budget expired, so the host terminated it and reported the generic runtime_startup_timeout instead of the mutation_lock_timeout the runtime had already computed. That is the assertion the Windows lane produced. Give a runtime that is still running at the ready budget a bounded window to publish its locator or its typed diagnostic, and read the published envelope during that window. The window is one more ready budget, so no new budget enters the code and a startup attempt waits at most twice the ready budget. When the window closes with nothing published the runtime is terminated and the generic timeout is still reported. A runtime that publishes its locator inside the window is now accepted instead of terminated, and the published envelope stays authoritative for the caller: mutation_lock_timeout reaches the shared remediation in start_goal.py where runtime_startup_timeout had its own text. Signed-off-by: xiaoan17 <anbc1224@gmail.com>
Record the gap the repair closes, the two acceptance points and how they are met, what the entry deliberately does not establish, and the validation that pins the behaviour. The repair is a diagnostic improvement rather than a correctness repair, and the entry does not qualify Windows CI as passing. Signed-off-by: xiaoan17 <anbc1224@gmail.com>
The dashboard-browser (2) lane failed once on a single lens-switch sample: 1345 ms against the frozen 1 s budget, while the other four samples in the same run measured 511-765 ms. The same lane on two other branches in the same window measured 377-765 ms, and the failing run's `checks` failure is its consequence: a failed browser lane skips dashboard-acceptance, and the aggregate gate then requires that job to have succeeded. The budget is frozen and stays as it is. The smoke renders an in-page fake runtime over synthetic status and map data, so this branch's Python runtime startup change has no path to the render timing. No content change. Signed-off-by: xiaoan17 <anbc1224@gmail.com>
The same scenario failed again on a fresh runner. The loaded lens switches measured 736, 521, 872, 977 and 1061 ms against the frozen 1 s budget; the first attempt measured 733, 511, 765, 1345 and 617 ms. Other branches' runs of the same lane in the same window measured maxima of 673, 713, 775 and 817 ms, so the budget carries about 30% headroom on a healthy runner and none on a loaded one. This branch's diff is Python-only. The dashboard-browser lane serves the dashboard with vite and answers the API inside the page, so it never executes the changed Python path, and the managed Effect runtime is not started by that lane. The shard-2 job ran about 20% slower end to end than another branch's shard-2 job in the same window, with every scenario duration higher, which is the signature of a loaded runner rather than a code cause. The frozen budget is not touched, and neither failure is relabelled. No content change. Signed-off-by: xiaoan17 <anbc1224@gmail.com>
…elsewhere The third attempt failed the same way (814, 792, 612, 607, 1213 ms), and the shard-2 job repeated its shape exactly: 22 scenarios in 788 s, against 644 s for 27 scenarios in shard 1 of the same run. That shape is not this branch's. The run of codex/quota-demand-loading from 08:12 today failed the same assertion on the same scenario (733, 551, 1515, 983, 592 ms) with the same split: 22 scenarios in 800 s for shard 2 against 831 s for 27 scenarios in shard 1, and 475 s for shard 3. A loaded shard-2 runner ends the shard early, and the loaded lens switch is what crosses the frozen budget. The budget is not touched and the earlier failures are not relabelled. No content change. Signed-off-by: xiaoan17 <anbc1224@gmail.com>
…missed exit The review is right about the attribution. The baseline reads the published envelope only when a poll *inside* the ready loop reports an exit; after the loop it polls once more, but only to decide whether to terminate the child, and it then always reports the generic timeout. So the recorded failure does not distinguish a child that had not published yet from a child that had published and exited without the parent observing it, and the entry claimed the first one as established. State both possibilities and keep the historical attribution undetermined. The repair covers both, so it does not depend on which one the recorded failure was: a late publication is read inside the grace window, and an envelope that was already published is read without waiting for the child to exit. Pin the second case with a test. The exit lands after the parent's last in-loop poll; the baseline reports the generic timeout for an envelope that is already in the capture, and the repair reports the exact code. Both new tests report the generic timeout on the baseline. Also state that the added wait is the startup path's own cost, not an absolute ceiling for the whole call: the startup lock, the Node probe and the outer retry each carry their own. Signed-off-by: xiaoan17 <anbc1224@gmail.com>
4cb54a1 to
ed1c2ae
Compare
|
JasonBuildAI
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · GPT-6.1 · OpenAI · self_reported
本次复审固定于 7743@ed1c2ae62f7e64b242f6c323fad72f43b5754208。我重新阅读了 6 个改动文件、相关 TypeScript 发布端与 Python/CLI 消费路径,复跑了三条决定性测试,并核对了当前 Windows 作业和失败 shard。先前 P2 要求修正的排他性因果解释已经修复;本次没有发现新的 blocking finding。
动机
受影响的是通过 CLI、Chat 或其他 Python 入口启动共享 Effect runtime、并依靠启动错误码选择修复动作的操作者。修复前,mutation lock 已给出精确诊断但就绪预算先到时,超过预算的调用方会收到通用的 runtime_startup_timeout,并可能被引导去排查错误方向;修复后,宿主会在一个有界的收尾窗口内继续等待已发布的 locator 或类型化错误信封,并报告 mutation_lock_timeout。可观察结果是:迟到发布与已经发布但错过退出的信封都能返回原精确码;窗口内没有任何发布时仍终止 runtime 并在最多两倍就绪预算后返回 runtime_startup_timeout。本 PR 不放宽现有就绪或 mutation-lock 预算,不改变重试策略、任务结算、租约或权限,也不宣称 Windows CI 的间歇失败已经消失。
旧行为丢掉的是 runtime 已经算好的诊断,而不是没有产生诊断。调用方因此看到不同的修复提示:精确的 mutation_lock_timeout 会落到共享的安装/修复指引,而 runtime_startup_timeout 有专门的超时说明。这个差异会影响真实排障,但不是业务正确性修复;本 PR 明确只修正诊断送达,并保留启动失败本身。
改动思路
入口仍是现有的 effect_runtime_request / _start_runtime。原有 ready 循环继续负责正常启动;预算耗尽后,新的 _await_startup_publication 才进入收尾窗口,依次检查 locator、子进程退出和已经发布的 typed envelope。错误分类仍由 TypeScript runtime 产生,Python 只读取既有 loopx_effect_runtime_startup_error_v0 信封,不建立第二套错误分类器。effect_runtime_request 继续拥有 retry-safe 重试决定,新的等待 helper 不会授权交易重放。
把等待放在现有 startup owner 内,是因为 locator、子进程生命周期、启动锁和错误信封本来就在同一临界区;若在 CLI 或调用方增加重试,会复制状态判断并可能启动第二个 runtime。实现复用既有 ready budget 作为 grace 上限,没有引入独立超时数字,也没有改变 TypeScript mutation-lock 预算。
留在这个 PR 的边界是“取回已发布的启动诊断并保持等待有界”。整个 Windows 间歇失败集合、全平台 CI 资格和父 roadmap 的其他恢复工作都不在这里。这里采用的架构判断是:等待与解析属于现有 startup invariant;精确错误码仍是 provider 已发布的既有协议;ledger 只是可审查的交付记录,不成为新的运行时 authority。现有 startup owner 足以承载这段逻辑,拆出新的协调层或后台观察器只会增加生命周期和竞态。
具体改动
关键代码讲解
loopx/control_plane/effect_runtime.py:82的STARTUP_DIAGNOSTIC_GRACE_SECONDS直接复用STARTUP_READY_TIMEOUT_SECONDS。它把单次启动尝试的最坏等待限制为两倍 ready budget,避免引入与现有启动预算无关的新常量;budget 仍由原 startup owner 决定。loopx/control_plane/effect_runtime.py:1166的_published_startup_failure提取了原有 exited 分支的安全解析:只接受匹配 schema、非空 code 的 typed envelope,保留runtime_host_permission_denied的特殊异常,不把任意 stderr 或 Node 堆栈当成业务诊断。loopx/control_plane/effect_runtime.py:1186的_await_startup_publication在 deadline 内先验证 locator,再检查进程退出与 typed envelope。已经发布错误的子进程会被终止并返回精确异常;无信封退出仍走runtime_exited_before_ready;窗口关闭且没有发布时返回None,由调用方执行最终补读、终止和通用超时。loopx/control_plane/effect_runtime.py:1340的 ready budget 耗尽分支把原实现一次性的 exit 检查替换为有界 grace。它接受窗口内迟到的 locator,也使“信封已发布但父进程错过 exit”不再退化成 generic timeout;同时保留原有 startup lock、Popen owner 和 retry policy。tests/control_plane/test_effect_runtime_integration.py:1253的新回归用真实 TypeScript runtime 和 live mutation lock 验证迟到精确码,另外两例分别固定“已发布但错过退出”和“完全不发布仍有界并 terminate”的分支。这些测试覆盖的失败形态与原 Windows 断言相同,但没有把一次成功运行扩张成 Windows CI 稳定性资格。
规格映射
规格依据是 docs/architecture/rfcs/ledger/loopx-overall-roadmap-v0/2026-10-04-s2-s12-startup-diagnostic.md,审阅修订为 04c5799b88f6c4cf12a224b7fa679f272e1e794e。下方按原检查点的验收内容映射,而不是用本 PR 新增的 ledger 反向定义要求:
| criterion_id | 原验收要求 | 当前 head |
|---|---|---|
exact-startup-code |
startup locator 无法发布时,失败必须走既有 typed startup envelope,并保留共享精确码 | implemented;_published_startup_failure 与 test_a_doomed_runtime_keeps_its_exact_code_after_the_ready_budget |
bounded-safe-envelope |
消息有界,且不得携带原始 Node 错误、locator 路径、token 或堆栈 | implemented;既有 _startup_diagnostic 限长/筛选,加上三条回归的泄露与边界断言 |
foreign-occupant-preserved |
失败只能清理自己的 start lock,不得修复或删除 foreign locator 与 live lock | implemented;新回归断言 foreign lock 原样保留、failed publisher 不声称 ready |
stable-exit-fallback |
没有 typed envelope 的退出仍报告 runtime_exited_before_ready |
implemented;_await_startup_publication 保留该分支,既有退出回归继续覆盖 |
新增双语 ledger 和 STATUS 计数是这次交付的可审查说明,不是独立规格来源。其内容已保留“尚未发布”和“已经发布但父进程错过退出”两种可能,不再把历史 Windows timeout 归因为唯一原因。
对主干的风险
最重要的行为变化是启动失败路径最多多等一个 ready budget。运行时仍在运行且不发布任何内容时,宿主会在 STARTUP_READY_TIMEOUT_SECONDS + STARTUP_DIAGNOSTIC_GRACE_SECONDS 后终止并保留通用超时;正常 ready 路径不增加等待。该代价已在 ledger 和 PR 说明中公开,并由受控时钟测试固定,不是隐式放宽 timeout。
具体的反例检查包括:live mutation lock 让 TypeScript runtime 在 5 秒后发布错误,当前 head 返回 mutation_lock_timeout 且不泄露 marker/token;父进程错过 exit 时仍读取已存在信封;完全不发布时仍在两倍预算后终止并返回 runtime_startup_timeout。我在当前 exact head 复跑这三例,结果为 3 passed、99 deselected、7.59 秒;此前完整 integration 为 101 passed、1 skipped,关联四文件为 197 passed、4 skipped;docs-governance-smoke、RFC 状态索引和双语差分检查也通过。
当前 GitHub rollup 仍有红项:多个 shard 的 native restart、dashboard/UTF-8 和 Lark timing 测试失败,shard 4/5 cancelled,pytest 与 merge-gate 随聚合失败;这些路径不在本 PR 的 6 个改动文件中,base 04c5799b88f6c4cf12a224b7fa679f272e1e794e 的 native restart 也失败,dashboard/UTF-8 的本地 base/head 对照呈现同一失败。与此同时,exact-head windows-powershell 作业成功,TypeScript coverage 与 dashboard acceptance 也成功。基于这些独立证据,我将现存红项视为外部/既有、与本次 invariant 无关的 merge-readiness hold,而不是本 PR regression;我不把 CI 红灯说成绿色,也不据此改变冻结门槛。
语义与 CI 对齐方面,改动没有增加 actor、租约、任务义务或重试 authority,只复用既有 startup error schema 和共享错误码;guidance_vs_obligation 中的 CLI 修复文案仍是 guidance,不是机器强制义务。没有发现未归属的当前失败需要本 PR 修复,也没有要求本 PR 承担 Windows lane 的长期稳定性资格。
我的整体评价
这次修复对长程工作有明确价值:调用方拿到可用于选择恢复动作的精确诊断,而不是在该重试、修复锁还是重装 runtime 之间猜测。用户可见结果是更准确的修复指引,代价是失败启动最多多等一个 ready budget;我将后者记录为 bounded tradeoff,而不是把额外等待藏在新超时里。
repository_reuse 判定为复用:现有 startup helper、typed envelope、错误类型和 mockable clock 足够,没有第二套状态 owner。observable_semantics 是有意的行为变化并已由三条分支测试和真实 runtime 边界验证;change_proportionality、code_volume 和 scope_fit 都与一个可独立审查、可回滚的启动路径切片相称。先前 P2 的双语说明与回归已经闭合,当前未发现 blocking finding,因此建议接受该 head。
合并仍需单独处理现有红检查,尤其是 shard/native-restart 与 dashboard 环境问题;APPROVE 不授予 merge authority,也不把该 PR 标成 merge-ready。若 head 变化,上述测试和规格映射需要重新确认。
English verdict: APPROVE — ed1c2ae62f7e64b242f6c323fad72f43b5754208 resolves the prior P2, preserves the typed startup diagnostic with a bounded grace, and has no new blocking finding; unrelated red CI remains a separate merge-readiness hold.
撤销本账号旧评审中已解决的唯一 P2。当前 head ed1c2ae 的双语 startup-diagnostic-grace ledger(英文20–31、81–84;中文18–24、64–66)及 PR 说明,已明确保留“尚未发布”和“已发布退出但父进程未观察到”两种历史时序,负载仅为可能因素,并披露额外一个 ready budget 及整个调用的其他等待成本。新增 test_an_envelope_published_before_the_deadline_is_still_read 固定原反例;本次独立源码/文档核对确认旧的排他性归因已消除。此核销仅关闭 review 5714926451 的文档阻断,保留原讨论、JasonBuildAI 在该 head 的 APPROVED 5722842303,以及仍需独立判断的 CI/Windows 资格。源码及测量叙述中 publication/exit 的个别强措辞可进一步澄清,但不再排除上述历史两分支,不作为旧 P2 的延续。
Goal and source
The
windows-powershelllane fails intermittently on operations that start theEffect runtime and wait for it to become ready. One retrievable run of that
lane (
1e3401591) failed withThat assertion is the whole subject of this PR: the runtime computed the exact
typed code, and the host reported a generic timeout instead.
The
S2/S12 startup diagnostic checkpoint
of 2026-10-04 established that a live mutation lock must fail with the exact
mutation_lock_timeoutcode, and explicitly did not qualify Windows.The gap
The TypeScript runtime publishes its typed startup envelope to stderr when it
gives up. Python reads that envelope only when
process.poll()shows the childhas already exited; otherwise it terminates the child at the ready budget and
reports the generic
runtime_startup_timeout.The envelope is published at the moment the child exits, so there is nothing
typed to read while the child is still running: at one second, three seconds
and five seconds the child is still running and stderr holds zero bytes, and
the envelope appears only once it exits, at about 5.8 s, together with exit
code 2.
runtime_startup_timeoutis produced whenever the parent does not observe thechild's exit inside the ready loop. The baseline reads the published envelope
only when a poll within that loop reports an exit; after the loop it polls
once more, but only to decide whether to terminate the child, and it then
always reports the generic timeout. The historical run therefore does not
distinguish two situations: the child had not published yet when the budget
expired, or the child had already published and exited and the parent had not
observed that exit before it left the loop. The same job's pytest step ran 439
cases in 5 min 31 s, and the chunk holding the failure took two minutes
against six seconds for the chunk before it, so the runner was heavily loaded —
a plausible contributor to a late publication, not a demonstrated cause.
The repair below covers both situations, so it does not depend on knowing which
one the recorded failure was.
The startup cost itself has ample headroom — measured on the Windows runner at
2f544f9692and on macOS atd3d9ad209d:nt)mutation_lock_timeoutposix)mutation_lock_timeoutSTARTUP_READY_TIMEOUT_SECONDSis 15.0 and the TypeScriptMUTATION_LOCK_TIMEOUT_MSis 5,000. Neither budget is the constraint, andneither is widened here. The lock in this path is held by a live owner, so the
owner-liveness check behaves correctly for it and is not touched.
The repair
loopx/control_plane/effect_runtime.pynow gives a runtime that is stillrunning when the ready budget expires a bounded window in which it may publish
either its locator or its typed diagnostic. The host reads the published
envelope during that window and reports the exact code it carries.
The window is bounded by a budget the repository already owns rather than by a
new number:
STARTUP_DIAGNOSTIC_GRACE_SECONDSis one more ready budget, so asingle startup attempt waits at most twice
STARTUP_READY_TIMEOUT_SECONDS.When the window closes with nothing published, the runtime is terminated and
the generic
runtime_startup_timeoutis reported, so the wait stays bounded.A runtime that publishes its locator inside the window is now accepted instead
of terminated. Before the repair, a runtime that was merely slow was killed at
the ready budget; that outcome is unchanged in kind, only later in time.
Two consequences a reviewer should read as intended rather than as regressions:
exact
mutation_lock_timeoutis now told to repair or reinstall LoopX,where it was previously told that startup timed out, because
loopx/cli_commands/start_goal.pygivesruntime_startup_timeoutits ownremediation text and
mutation_lock_timeoutfalls through to the shareddefault.
instead of once, and
effect_runtime_requestretries a startup failure oncefor a retry-safe call. The retry policy is unchanged here. That is the wait
this startup path adds, not an absolute ceiling for the whole call: the
startup lock, the Node probe and the outer retry each carry their own cost.
Acceptance evidence
The repair is pinned by a test that reproduces the observed failure without a
Windows runner. With the ready budget shortened and a live mutation lock held
by the current process, the pre-repair host reports the generic timeout, and
the assertion is the one the Windows lane produced:
After the repair the same fixture delivers the exact
mutation_lock_timeout,the published envelope, and the preserved live lock.
A second test pins the other situation the historical record cannot separate:
an envelope the runtime published before the deadline, whose exit the parent's
poll missed, is still read — the parent polls once more after the loop, but
the baseline never reads the capture again. Both tests report the generic
timeout on the baseline.
A third test pins the bound: a runtime that publishes nothing is terminated and
still reports
runtime_startup_timeoutafter exactly one ready budget plus onegrace window.
Validation
The maintainability ratchet caught the first form of this change: the grace
window had pushed
_start_runtimepast the repository's decision-functionlimit. The window now lives in
_await_startup_publication, and the ratchetreports no oversized decision function.
Changed surfaces and boundary notes
loopx/control_plane/effect_runtime.py— runtime startup path. No publicprotocol, schema or state vocabulary changes; the diagnostic codes involved
(
runtime_startup_timeout,mutation_lock_timeout) already existed.tests/control_plane/test_effect_runtime_integration.py— the file theWindows lane runs explicitly, so both new tests run on that lane.
docs/architecture/rfcs/ledger/loopx-overall-roadmap-v0/2026-10-30-s2-s12-startup-diagnostic-grace.mdand its Chinese mirror, plus the derived status index.
No first screen changed: this is a runtime startup path, and there is no user
interface, screenshot or preview to show for it.
The measurement that motivated the repair is re-derivable rather than taken on
trust: the commit that produced the two figures is reachable as
codex/measurement-archiveon the fork, and it raises a deliberateAssertionErrorcarrying the measurement, because the Windows lane runs pytestwithout
-sand would otherwise capture the output.