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
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.