Repository navigation
Conversation
715a565 to
36d0033
Compare
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:历史失败的排他性归因不成立。 本次审阅固定在 36d0033bad3efe49181d475c3075627ba851feee,对比主干基线 0ad08b15dd7331051c93566075561a390a9cd7e2,完整阅读了全部 4 个改动文件及相关启动实现。
动机 / Motivation
受影响的是排查 LoopX 启动失败的维护者和运行 Windows 自动化的操作者。
原先一次启动失败只留下通用超时,排查者不知道精确错误丢在何处;这份文档尝试记录诊断产生和送达的时序,减少后续重复调查。
文档确实整理了两个宿主的成功测量与锁语义,但尚未证明历史失败一定发生在子进程仍运行时。
本PR只增加双语调查记录及状态索引,不修改运行时预算、锁属主判定或生产启动行为,也不构成 Windows 原生资格认证。
改动思路 / Approach
把成功测量、类型化错误信封的产生、Python 的观察时机和后续修复建议放在同一份双语记录中,有实际维护价值。live-lock fixture 的属主就是当前存活进程,不能为了让测试快一些把它当陈旧锁回收;文档在这一点上保留了正确的边界。没有必要为本次调查额外引入运行时框架或扩大到完整 Windows 认证。
但“子进程写出错误”与“父进程在预算内观察到退出”是不同事件。基线 _start_runtime 在 ready 循环结束后调用 poll(),这个结果只决定是否 terminate();随后始终抛出 runtime_startup_timeout。因此最终 poll() 已经得到退出码 2,也可能返回同一个通用错误。成功样本的 5.813/5.140 秒只能说明那些样本按时送达,不能确定另一历史失败时 child 的实际状态。
具体改动 / Changes
关键内容讲解
两个 2026-10-30-s2-s12-windows-startup-delivery ledger 分别记录英文和中文调查;两个 STATUS 是相应的派生索引更新。没有生产、测试实现或配置改动。正文把测量方法、live owner 的锁语义、失败传播窗口和非目标放在一起,结构上是一个完整的调查切片。
采用的既有合同是 docs/architecture/rfcs/ledger/loopx-overall-roadmap-v0/2026-10-04-s2-s12-startup-diagnostic.md,固定修订 0ad08b15dd7331051c93566075561a390a9cd7e2。原文没有独立编号,以下只是本次映射标签:diagnostic-transport 要求保留有界、类型化启动诊断,缺信封时不伪造原因;qualification-boundary 明确该交付不认证 Windows 或全套 CI。这两项边界都应保留,不能用新 ledger 对历史时序的推断取代实际观测。
P2:请修改英文第 85–102 行及中文对应段落。 “该码只在 ready 截止时 child 仍运行才产生,所以历史 child 必定超过 15 秒未退出”与源代码不符;“截止时补读一定拿不到信封”也超出了测量所能支持的范围。最小修复是分别描述:child 当时确实尚未发布;或者 child 已发布/退出但 parent 尚未观察到。历史运行缺少区分这两者的记录,应保留为未确定,而不是认定高负载已经证明根因。
对主干的风险 / Risks
我通过真实 Python 子进程和真实 stderr 临时文件构造了截止边界,仅对父进程的 monotonic 调度使用合成时钟:ready deadline 为 1,child 在最后一次等待内发布合法 mutation_lock_timeout 信封并以 2 退出,parent 在 1.1 恢复。实际基线函数返回 runtime_startup_timeout,最终 child 已退出,生产 capture 读取次数为 0,启动锁正常清理。这是代码可达性的反例,不是对历史 Windows 运行的重演。
相同探针在 #7743 当前 grace 实现上返回精确码,因此这个 finding 不否认 grace 方案可以修复传播缺口;它要求调查记录不要把一种可能时序写成已证事实。更小且足够的改动是校正文案,不需要在这份纯文档 PR 里扩展预算、重写 PID liveness 或夹带实现。
验证:generate_rfc_status_index.py --check、对固定基线的 check_rfc_bilingual.py 和 docs-governance-smoke.py 均 PASS;原生产文件相对基线无变化。有界调度反例 PASS(成功否证唯一归因)。历史失败的实际 child/parent 时间线 UNVERIFIED,本轮没有原生 Windows 执行。
我的整体评价 / Verdict
文档作为调查资料有价值,当前主要问题是结论强于证据。长程执行本身未改变;维护者体验的改善仍受错误归因限制,修正后才是可信的排查参考。这里不要求删掉测量或先完成 #7743,只要求将可观察事实、推断和剩余未知写清楚。本次检查的 docs/architecture/rfcs/ledger/loopx-overall-roadmap-v0/2026-10-30-s2-s12-windows-startup-delivery.md 不改变已渲染的产品页面;无需用截图证明内部行为。 未进行产品页面视觉检查,因为本次没有页面改动。
English verdict: REQUEST_CHANGES — 36d0033bad3efe49181d475c3075627ba851feee has one P2 documentation finding: the historical timeout does not prove the child was still running at the deadline. Documentation checks passed; a real-child/synthetic-clock counterexample confirms the omitted branch. Historical Windows timing remains unverified.
Review response: the attribution is correctedThe finding is accepted, and the correction is on the branch ( What was wrong. The entry stated that What changed (both the English entry and its Chinese mirror, and the PR
Unchanged. The entry still changes no runtime behaviour, still widens no |
The Windows lane fails intermittently on operations that start the Effect runtime and wait for it to become ready. Measuring the startup cost on the Windows runner shows node cold start at 0.406 s against a 15 s ready budget, and the live-lock case returning the exact mutation_lock_timeout code on that same run. That measurement refutes the earlier conclusion that node cold start plus the 5 s lock wait exceeds the ready budget on Windows. Neither budget should be widened. The failing run's own job log is retrievable and shows runtime_startup_timeout, which is produced only when the ready budget expires while the child still runs; that 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 loaded and the child could not finish its mutation-lock wait inside the budget. Tracing the child shows the envelope appears only at exit, about 5.8 s in, so reading the capture at the budget would find nothing. Waiting for the exit instead yields the exact code every time, and the extra wait is bounded: the child exits at 5.13-5.17 s whatever the ready budget was, because what it waits for is the mutation-lock budget. That identifies a bounded grace window as the repair, with the acceptance points it would have to satisfy. The repair itself is not made here, and the measurement that produced the figures stays reachable so they can be re-derived. Signed-off-by: xiaoan17 <anbc1224@gmail.com>
The review is right that the entry's attribution was exclusive where the source does not support it. The baseline reads the published envelope only when a poll *within* the ready loop reports an exit; after the loop it polls once more, but that poll only decides whether to terminate the child, and the function then reports the generic timeout without reading the capture again. So the recorded Windows failure does not distinguish a child that had not published yet from a child that had published and exited with the parent not observing it. State both possibilities, keep the attribution undetermined, and describe the loaded runner as a plausible contributor rather than a demonstrated cause. Reading the capture at the budget recovers the envelope in the second situation only, and a repair with a bounded grace window covers both, so it does not depend on knowing which one occurred. Signed-off-by: xiaoan17 <anbc1224@gmail.com>
6ceeefc to
7f4edaf
Compare
Goal and source
The
windows-powershelllane fails intermittently on operations that start theEffect runtime and wait for it to become ready. The failing set differs per run:
test_effect_runtime_integration'slive_lockcase,test_shadow_observation_retirement,test_chat_manager_inspection. Across six Windows lane runswhose job logs are still retrievable, the failing file set was
test_self_update_runtime_activationalone (three runs), that file togetherwith
test_shadow_observation_retirement(one run), that pair together withtest_effect_runtime_integration(one run), andtest_chat_manager_inspectionalone (one run).
The
S2/S12 startup diagnostic checkpoint
of 2026-10-04 recorded that a live mutation lock must fail with the exact
mutation_lock_timeoutcode, and explicitly did not qualify Linux or Windows CIas passing. This entry records what that checkpoint did not establish on
Windows, and corrects the premise that produced an earlier wrong conclusion
about the startup budgets.
Measured
The startup cost was measured on the Windows runner at
2f544f9692and onmacOS at
d3d9ad209d:nt)mutation_lock_timeoutposix)mutation_lock_timeoutThe two revisions differ in
effect_runtime.py, but only in theruntime-source metadata read,
_control_plane_rootand_request_with_info.The startup path itself —
_start_runtime,STARTUP_READY_TIMEOUT_SECONDS,_read_startup_stderrand theprocess.poll()check — is unchanged betweenthem, so both hosts are measured on the same startup code.
The live-lock total is the ready startup plus the TypeScript mutation-lock wait.
Both budgets have ample headroom:
STARTUP_READY_TIMEOUT_SECONDSis 15.0 andMUTATION_LOCK_TIMEOUT_MSis 5,000.This corrects an earlier conclusion. The hypothesis that node cold start
plus the 5 s lock wait exceeds the 15 s ready budget on Windows is false. The
cold start is 0.406 s, and the live-lock case returned the exact
mutation_lock_timeoutcode on that same run. Neither budget should bewidened to explain this failure.
Where the exact diagnostic is lost
TypeScript computes the typed code and publishes it:
effect_runtime_server.tsfailStartupwrites the startup envelope to stderrwith
writeSyncand then callsprocess.exit. Python reads that envelope onlywhen
process.poll()shows the child has already exited(
effect_runtime.py); otherwise it terminates the child at the budget andreports the generic
runtime_startup_timeout.So the exact code is produced but not delivered when the poll loop does not
observe the exit inside the budget.
The lock in this path is not a stale lock. The fixture writes the current
process's own PID as the lock owner, so the owner is alive and
reclaimStaleMutationLockcorrectly refuses to reclaim it; waiting out the5 s mutation-lock budget is the intended behaviour on every platform. The
measured live-lock total is 5.813 s on Windows and 5.140 s on macOS against a
15 s ready budget, and the exact code was delivered on both, so the budget is
not the constraint in the measured runs.
The intermittent Windows failure at
1e3401591is a different situation.Its job log is retrievable and shows
assert 'runtime_startup_timeout' == 'mutation_lock_timeout'. That code isproduced whenever the parent does not observe the child'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
that poll only decides whether to terminate the child, and the function then
reports the generic timeout without reading the capture again. 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.
Reading the capture at the moment the budget expires recovers the envelope in
the second situation only. At one second, three seconds and five seconds the
child is still running and stderr holds zero bytes, and the envelope appears
only once the child exits, at about 5.8 s, together with exit code 2: a child
that has not exited has published nothing, and a child that has exited leaves
the envelope in the capture for the baseline to leave unread.
It can be recovered by letting the child finish first, or by reading the
capture again once the child has exited. Waiting for the exit instead of
terminating at the budget yields the exact code in every measured case, and
the extra wait is bounded rather than open-ended: the child's exit lands at
5.13 s to 5.17 s regardless of whether the ready budget was 0.5 s, 1 s or 2 s,
because what the child waits for is the TypeScript mutation-lock budget. A
repair would therefore give the child a bounded grace window after the ready
budget, read the envelope if it appears, and report the generic timeout only
when the child does not finish inside that window. Such a repair covers both
situations above, so it does not depend on knowing which one the recorded
failure was. That repair is not made here.
What this PR does and does not do
It adds one bilingual execution-ledger entry under
ledger/loopx-overall-roadmap-v0/, plus the derived RFC status index, andchanges no runtime code.
No budget change is proposed or qualified — the measurements show both budgets
are sufficient. No owner-liveness change is proposed here: the lock in this
path is held by a live owner, so that check behaves correctly for it.
The intermittent Windows failure is now attributed to a child that did not
exit inside the ready budget on a loaded runner, and a bounded grace window
after that budget is the repair this entry identifies. The repair itself is
not made here: giving the child a grace window changes how long a failed
startup blocks its caller, and that needs its own acceptance and a caller
that tolerates the longer wait.
Two acceptance points follow from it. The caller now waits longer, so the
grace window has to be bounded by something the repository already owns
rather than by a new constant. And the user-visible guidance changes with
the code:
loopx/cli_commands/start_goal.pygivesruntime_startup_timeoutits own remediation text, while
mutation_lock_timeoutfalls through to theshared default, so a caller that receives the exact code would be told to
repair or reinstall LoopX where it was previously told that startup timed
out. That difference is the intended consequence of recovering the exact
code, and it has to be read as such rather than as a regression.
The delivered typed code is a diagnostic improvement, not a correctness repair:
the runtime still fails to publish its locator, and the caller still fails to
start. Windows CI is not qualified as passing by this measurement, and the
intermittent failure set is preserved rather than reclassified.
The measurement itself was a temporary diagnostic, removed after the numbers
were read; it is not a qualification artifact in this repository. The commit
that produced the numbers is reachable as
codex/measurement-archiveon thefork, so the two figures can be re-derived rather than taken on trust: it
raises a deliberate
AssertionErrorcarrying the measurement, because theWindows lane runs pytest without
-sand would otherwise capture the output.Validation
Ledger entries are append-only history, so this is a new entry that states what
the 2026-10-04 checkpoint did not establish, rather than an edit to that entry.