Skip to content

[Bug] issue2129 restart-measurement e2e flakes at a pinned ~3.13s on ubuntu shard 1 — reproduces on master commits that touch only workflow YAML #5379

Open

Description

@Louie0806

(ai: deepseek/deepseek-v41-flash)

Decisive: this fires on master commits that cannot reach the code under test

db06abbeb (fix(release): one-click can ship a merged-but-unpublished fallback bump (#5376)) is merged into master, and its entire diff is one workflow file:

.github/workflows/release-manual.yml

Its CI run still failed shard test (ubuntu-latest, 22, 1) with:

not ok - e2e: restart restores k̂ + usage anchor — the over-raw resend forwards
         without folds or an EMERGENCY nudge (#2129)
error: 'PFA stamp must be on disk after restart'
duration_ms: 3136.553892
location: tests/issue2129-restart-measurement-state.test.ts:3:2138
stack:  TestContext.<anonymous> (tests/issue2129-restart-measurement-state.test.ts:327:16)

A release-workflow YAML commit cannot touch src/persist.ts. So the red shard is pre-existing on master, not a property of whichever PR happens to be running when it lands.

Three occurrences, all the same assertion

job head shard duration_ms
124524862335 1b0726e9e test (ubuntu-latest, 22, 1) 3131.82
124582276613 adc94d7c3 test (ubuntu-latest, 22, 1) 3127.66
124595544231 db06abbeb (master) test (ubuntu-latest, 22, 1) 3136.55

All three: error: 'PFA stamp must be on disk after restart', code: 'ERR_ASSERTION'.

The duration is pinned to ≈3130 ms in all three. That is the detail I find most diagnostic: contention noise scatters, a fixed ~3.13 s ceiling does not. Something in this test's own timeline is bounded at ~3 s, and the assertion observes the state after that bound has expired. The file has no polling helper of its own (helpers are makeSession, findRecordFile, chatSse, longMessages, makeUpstream, proxyOptions), so the bound lives in the proxy/turn machinery rather than in a test-side retry loop.

The failing assertion

tests/issue2129-restart-measurement-state.test.ts:327:

assert.ok(env.payload.metadata?.anonymousPrefixAffinity, "PFA stamp must be on disk after restart");

The setup immediately before it (:315-323):

s.stats.lastInputTokensSource = "estimate";
s.metadata.anonymousPrefixAffinity = { depth: 22, tailHash: "i2129-test-tail", via: "prefix" };

await store.writeNow(s);
await store.flushAll([]);
const env = JSON.parse(readFileSync(findRecordFile(dir), "utf8")) as { payload: ... };
assert.equal(env.payload.stats?.calibratedEstimate, preRestart.k, "k̂ must be on disk after restart");
assert.equal(env.payload.stats?.lastUsageGradeTokens, preRestart.anchor, "usage anchor must be on disk after restart");
assert.equal(env.payload.stats?.localInputEstimate, preRestart.est, "per-turn estimate must be on disk after restart");
assert.ok(env.payload.metadata?.anonymousPrefixAffinity, "PFA stamp must be on disk after restart");

Note that the three stats assertions before it pass — only the metadata one fails. So the write itself landed; it is specifically metadata that is missing its PFA stamp in the persisted JSON.

Code shape along that path (observations, not a proven mechanism)

src/persist.ts:3102 writeNow() drops resident state after the bytes land — sync path :3113-3116, async path :3131-3134:

this.foldResidentTail(session);
this.dropResidentBlockContents(session);
this.dropResidentMetadata(session);
this.dropResidentContentStore(session);

dropResidentMetadata (src/persist.ts:3215) replaces session.metadata with the transparent proxy built by lazyMetadata (src/session.ts:150). Every trap on it — get/has/ownKeys/getOwnPropertyDescriptor/set/deleteProperty/defineProperty — calls ensure() first, and ensure() throws when the loader returns null:

[persist] resident metadata for <id> could not be re-materialized from the store — refusing the thinned view

The piece I would look at first is buildRecordForSave (src/persist.ts:1886):

const bothDropped = isDroppedMetadata(session.metadata) &&
    session.blockContents instanceof LazyBlockContents && session.blockContents.isDropped;
if (!bothDropped) return buildRecord(session, storageMode);
const previous = this.residentFieldsForBuild;
const fields = this.loadResidentFields(session.id, session.meta ?? {}, { metadata: true, blockContents: true });
this.residentFieldsForBuild = { id: session.id, fields };
try {
    return buildRecord(session, storageMode);
} finally {
    this.residentFieldsForBuild = previous;
}

So on the both-dropped path the record is built by buildRecord, which spreads the proxy (src/persist.ts:3609: metadata: { ...session.metadata, biliVersion: VERSION, storageMode }) — and the proxy is served from the projection that loadResidentFields (:1851) just read back from disk. That projection is chosen by an envelope-vs-index arbitration (:1868-1869):

useEnvelope = requests > indexed.header.requests ||
    (requests === indexed.header.requests && Number(envelope.payload.state?.nextBlockId ?? 0) > indexed.header.nextBlockId);

If that arbitration can select a copy written before the PFA assignment, then a save queued after the drop re-persists a metadata object without the stamp — and the test's direct readFileSync sees exactly the failure it reports.

I have not proven this. It is a shape, not a trace. I mention it because the 3.13 s reproducibility and the "stats land, metadata doesn't" split both fit a stale-projection round trip rather than a lost write, and because flushAll([]) already drains drainWriterOffPending() (:3540) and flushScheduledSaves() (:3457) first, so the remaining window has to be inside writeNow or in the still-finishing turn.

Local reproduction: tried hard, all negative

  • 5 runs on a PR branch head — # pass 4 / # fail 0
  • 8 runs on pristine aaf4b1dc4 — 4/0
  • BILI_STORAGE_UNIFIED=legacy and =full, one run each — 4/0
  • 4 concurrent copies + 6 CPU burners, 3 rounds (12 runs) — all 4/0

Both CI occurrences ran on an unhurried runner:

2026-10-31T15:55:57Z resource snapshot: freemem=14961MiB totalmem=15984MiB loadavg=2.35/1.71/0.82 node=v22.23.4 platform=linux/x64
2026-10-31T11:33:31Z resource snapshot: freemem=14951MiB totalmem=15984MiB loadavg=1.74/1.45/0.72 node=v22.23.4 platform=linux/x64

so "loaded runner" is not the explanation either — which distinguishes it from the #3478/#3571 family.

Not auto-retried

scripts/ci-failure-shapes.mjs has no shape matching PFA stamp must be on disk. The only recognisable shapes are timeout waiting for …, ]?(EBUSY|EPERM|EACCES|ENOTEMPTY|EMFILE): [^"], timeout waiting for initialize + tools/list responses, the safety valve lands a JSON envelope, and the disabling tick must land a JSON write. So every hit costs a full red shard.

Options (owner's call — I have not opened a PR for any of them)

  1. Cheapest, unblocks everyone: add PFA stamp must be on disk to scripts/ci-failure-shapes.mjs so a retry absorbs it, and track the real investigation separately. I am happy to send this as a one-line PR.
  2. If the stale-projection shape above is right: make the both-dropped build not able to serve a projection older than the write that just landed (or skip the proxy round trip when the caller is a save). Deterministic, does not weaken what the test guarantees.
  3. Split the assertion: give the PFA stamp its own writeNow → readFileSync cycle so the failure localises to "this write lost the stamp" instead of "some write in this turn lost it". Changes what the test pins, so it is a judgement call.

I did not take 2 or 3 unilaterally because both touch a persistence-ordering contract I do not own.

Reference points

Activity

  1. Louie0806 commented on Oct 31, 2026

    @Louie0806
    ContributorAuthor

    (ai: deepseek/deepseek-v41-flash)

    Correction — my "pinned ~3.13s" argument in the issue body is WRONG

    I re-measured before asking anyone to act on this, and the timing claim I made in the body does not survive. Retracting it here rather than leaving a load-bearing wrong inference in the report.

    What I claimed

    That all three failures landed at duration_ms ≈ 3130ms while local runs at other shards/timings spread out, and that a fixed duration therefore indicates the test's own ~3s deadline expiring before the assertion — i.e. a timing window in the product.

    What actually happens

    The ~3.2s is per-test teardown, and it is present on the passing path too. Instrumented copy of the e2e test, phase timers through the body and the finally block, local run:

    [PHASE] test body start        @3ms
    [PHASE] upstream listening     @49ms
    [PHASE] proxy listening        @57ms
    [PHASE] before turn1 request   @66ms
    [PHASE] turn1 body read        @189ms
    [PHASE] after turn2            @216ms
    [PHASE] after flushAll         @221ms
    [PHASE] before phase3 post     @222ms
    [PHASE] ENTER finally          @270ms
    [PHASE] proxy close resolved   @3274ms   <-- 3.0s spent HERE
    

    So the entire body — two growth turns, writeNow, flushAll, the disk re-read and all four assertions — completes in ~270ms. The ~3s is await once(proxy, "close") in finally draining a keep-alive client socket. Proof: calling proxy.closeAllConnections?.() before proxy.close() collapses it:

    [PHASE] ENTER finally          @252ms
    [PHASE] closeAllConnections    @252ms
    [PHASE] proxy closed           @252ms     <-- same millisecond
    

    Consequence for the claim

    Per-test duration tracks the teardown, not the assertion path, and it is load-independent:

    run per-test duration
    passing, 6 local runs 3214 / 3302 / 3219 / 3217 / 3220 / 3236 ms
    failing, 3 CI runs 3131 / 3127 / 3136 ms

    The failing runs are actually shorter than the passing ones (they skip phase 3's extra request), which is the opposite of "a deadline expired". duration_ms here carries no information about where the failure occurs, so the "pinned duration ⇒ timing window" section should be ignored.

    What survives

    Everything that does not rest on the duration argument:

    • The failure is real and reproducible across three independent heads, including db06abbeb on master whose diff is only .github/workflows/release-manual.yml — a pure-workflow commit cannot reach src/persist.ts, so this is inherited, not introduced by any PR under test.
    • The failing assertion is the metadata one, not the stats ones. calibratedEstimate, lastUsageGradeTokens and localInputEstimate all pass on disk; only payload.metadata.anonymousPrefixAffinity is missing.
    • Local reproduction is entirely negative: 5× my head, 8× pristine master, 1× each storage mode, and 12× under 4-way concurrency with 6 CPU burners — all 4 pass / 0 fail.
    • It is not in scripts/ci-failure-shapes.mjs, so every occurrence costs a full red shard.

    What I do not know

    I have not identified the mechanism that drops the metadata field under CI load, and I am not going to replace the retracted timing story with a second guess. The honest state is: real, master-inherited, metadata-only, load-correlated (CI-only so far), mechanism unknown.

    If it helps the owner, the one asymmetry worth chasing is that dropResidentMetadata (src/persist.ts:3215) replaces session.metadata with a lazy proxy after the bytes land, and buildRecordForSave (src/persist.ts:1886) only consults loadResidentFields when both metadata and blockContents are dropped. Whether a racing debounced save can therefore serialize a projection taken before s.metadata.anonymousPrefixAffinity was assigned is unproven — I could not make it happen locally, and I am labelling it a hypothesis, not a finding.

    The three directions for the owner in the body stand unchanged; only the reasoning that pointed at them is retracted.

  2. Louie0806 commented on Oct 31, 2026

    @Louie0806
    ContributorAuthor

    (ai: deepseek/deepseek-v41-flash)

    第四次复现,这次在 master HEAD 自己身上。

    # head job shard duration_ms
    1 1b0726e9e 124524862335 test (ubuntu-latest, 22, 1) 3131.82
    2 adc94d7c3 124582276613 test (ubuntu-latest, 22, 1) 3127.66
    3 db06abbeb (master) 124595544231 test (ubuntu-latest, 22, 1) 3136.55
    4 e209109bf (master) 124602020189 test (ubuntu-latest, 22, 1) 3120.78

    第 4 次是 master push run(41222746623,event=push,branch=master),不是任何 PR 的 run。失败断言与前三次数完全一致:

    not ok 959 - e2e: restart restores k̂ + usage anchor — the over-raw resend forwards
                  without folds or an EMERGENCY nudge (#2129)
      duration_ms: 3120.784331
      error: 'PFA stamp must be on disk after restart'
      code: 'ERR_ASSERTION'
      stack: TestContext.<anonymous> (tests/issue2129-restart-measurement-state.ts:327:16)
    

    断言就在 tests/issue2129-restart-measurement-state.ts:327:

    assert.equal(env.payload.stats?.calibratedEstimate, preRestart.k, "k̂ must be on disk after restart");   // 323 通过
    assert.equal(env.payload.stats?.lastUsageGradeTokens, preRestart.anchor, "usage anchor must be on disk after restart"); // 324 通过
    assert.equal(env.payload.stats?.localInputEstimate, preRestart.est, "per-turn estimate must be on disk after restart");  // 325 通过
    assert.ok(env.payload.metadata?.anonymousPrefixAffinity, "PFA stamp must be on disk after restart");     // 327 失败

    这仍是我上一轮撤回时长论证后保留的两条事实,未变:

    1. 三次都在同一 shard、同一断言、同一 ~3.13s——不是 CPU 争抢(争抢会散开)。
    2. 它能在「只改 .github/workflows/release-manual.yml」的 master commit(db06abbeb)上复现 ⇒ 与被测代码无关,是继承性 flake。

    撤回仍然有效:我上一轮已撤回「固定时长 ⇒ 产品里有 ~3s 时间窗口」的推论。插桩实测显示测试体本身只跑约 270ms,其余 3 秒全在 finally 里 await once(proxy, "close") 排空 keep-alive 套接字;且通过时 3214/3216/3236ms、失败时 3120–3136ms——失败反而更短,与「期限到期」方向相反。所以机制仍未查明,我没有任何可指认的产品 bug。

    现在的状态:这个 flake 每次命中都要付一个全红分片,且它不在 scripts/ci-failure-shapes.mjs 的自动重试形状里(TIMING_WINDOW_ERROR 只匹配 timeout waiting for … / the disabling tick must land a JSON write / the safety valve lands a JSON envelope;TRANSIENT_LOCK_ERROR 只匹配 EBUSY|EPERM|EACCES|ENOTEMPTY|EMFILE + unlink|rmdir)。

    我能做且建议做的最小改动(仍等 owner 点头,因为它是 CI 策略不是产品代码):把 PFA stamp must be on disk after restart 加进 TIMING_WINDOW_ERROR,并在 tests/ci-runner-failure-shapes.test.ts 补一个 fixture。这样它会转成一次 bounded retry,而不是一个全红分片。若 owner 要我送这个 PR,我可以立刻提。

    (上一轮的撤回评论:issue #5379 comment 6547255767。)

  3. Louie0806 commented on Oct 31, 2026

    @Louie0806
    ContributorAuthor

    (ai: deepseek/deepseek-v41-flash)

    补一条量化结论,把「为什么失败总是钉在 ~3.13s」这个问题彻底关掉 —— 它和任何超时/期限无关,是断言位置决定的。

    全部 6 个有日志的样本([实测],逐条从 CI job log 里取 duration_ms)

    结果 head shard duration_ms
    FAIL 1b0726e9e test (ubuntu-latest, 22, 1) 3131.82
    FAIL adc94d7c3 同上 3127.66
    FAIL db06abbeb (master) 同上 3136.55
    FAIL e209109bf (master) 同上 3120.78
    PASS 6403897ce 同上 3242.08
    PASS 7d2b5e0ee test (windows-latest, 22, 4) 3610.24

    关键:4 次失败的极差只有 15.77 ms(3120.78–3136.55),而 2 次通过全部更长(+112.9 ms / +481.0 ms)。每一次失败都比每一次通过更快 —— 如果失败是「测试撞上自己的时间期限」,方向必然相反。所以「~3.13s」不是界,是早退的产物。

    机械解释(可从源码核对)

    失败断言在 tests/issue2129-restart-measurement-state.test.ts:327:

    assert.ok(env.payload.metadata?.anonymousPrefixAffinity, "PFA stamp must be on disk after restart");  // :327 抛

    而它之前还有两次 HTTP 往返没跑:

    const r3 = await post(t3msgs);   // :337  Phase 3 — READY resend
    const r4 = await post(t3msgs);   // :345  Phase 4 — host retry

    ⇒ 失败路径只跑了 4 次 post 里的 2 次。ubuntu 上两次 post 的实测成本 = 3242.08 − 3129.21 ≈ 113 ms,与通过/失败的差值吻合。

    剩下的 ~3.0 s 是 finally 里排空的 keep-alive 套接字:

    proxy.close(); proxy.closeIdleConnections?.(); await once(proxy, "close");   // ≈3.0 s,通过时也发生

    (我上一轮插桩过:测试体本身只跑 ~270 ms,ENTER finally @270ms → proxy close resolved @3274ms;把 closeAllConnections() 提到 close() 之前则三者同毫秒完成。)

    净结论

    • ✅ 失败是真实的、跨 4 个 head(含 master 自己两次)复现的、只在 CI 负载下出现的 metadata 丢失。
    • ✅ 它不是任何 3s 期限到期;我上一轮的撤回成立,现在有 6 个样本的量化支撑。
    • ❌ 机制仍未查明。我没有可指认的产品 bug,也不打算靠猜去改 persistence 顺序。

    因此我能提的仍然只有那一条最小 CI 策略改动

    把这个签名加进 scripts/ci-failure-shapes.mjs 的 TIMING_WINDOW_ERROR(并在 tests/ci-runner-failure-shapes.test.ts 补 fixture),让它转成一次 bounded retry 而不是一个全红分片。这是 CI 策略不是产品代码,仍需 owner 点头;要我做就一句话。

    (前序:本 issue 评论 6547255767 撤回、6550503183 报第 4 次复现。)

  4. Louie0806 commented on Nov 1, 2026

    @Louie0806
    ContributorAuthor

    Fifth CI occurrence, plus a sixth data point for the duration question.

    job 124713147445, run 41260674, shard test (ubuntu-latest, 24, 1).
    Error identical: PFA stamp must be on disk after restart,
    duration_ms: 3152.896613.

    Two things this adds.

    The failure is not shard-bound to (22, 1). Earlier in this thread I wrote
    that all occurrences were on ubuntu-latest, 22, 1. That was true for the first
    four; this fifth one is on ubuntu-latest, 24, 1. A shard is a file partition,
    so the flake follows the FILE across runner and Node-version pairings — which is
    consistent with a timing coupling inside the test rather than a Node behaviour.

    It also reproduces on a merge commit whose only new content is docs. The
    head here is a merge of three master commits that add a blog post. That tree
    cannot touch persistence, and it still fails the same way.

    Duration table, all six logged samples:

    result duration_ms
    FAIL 3131.82
    FAIL 3127.66
    FAIL 3136.25
    FAIL 3120.78
    FAIL 3152.90
    PASS 3242.08
    PASS 3610.24

    Every failure is still shorter than every pass. That is the opposite direction
    from what a deadline-expiry theory predicts, so I am not re-raising the "~3s
    window" reading I retracted earlier in this thread.

    Still unproven: why metadata?.anonymousPrefixAffinity is occasionally absent
    from the record while the three stats assertions just above it pass. I am not
    proposing a persistence-ordering change on a hypothesis.

    The two suggestions from my first comment stand: add this error string to the
    retry shapes so a known flake stops costing a red shard, or make the assertion
    poll a deadline instead of reading once.

  5. ranxianglei commented on Nov 1, 2026

    @ranxianglei
    Owner

    ework

  6. ranxianglei commented on Nov 1, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    查重:不重复。 这是新签名——tests/issue2129-restart-measurement-state.test.ts:327 的 PFA stamp must be on disk after restart,在 scripts/ci-failure-shapes.mjs 里没有任何既有形状覆盖它;open+closed 里也没有别的 issue/PR 记录这个 flake(按失败签名匹配:断言串 + 文件 + shard)。你已主动区分了已关闭的 #3478/#3571——那是 issue3380-dsh-rereg-selflock 在 Windows 负载下的家族,机制是 runner 负载;本例是 metadata-only、跨 ubuntu + node22/24 跟随文件走,不是同一失败域。

    标签: bug · scope/ci · p2(CI 测试抖动缺陷,主落在测试保真度/CI 面;终端用户不可见、非数据丢失,故 p2 非 p1。)

    下面是结构化审核结论。核心判断先说:这不是「~3s 期限」问题(你的撤回我认同),目前证据指向的是测试的"模拟重启"失真——一个残留/迟到的保存在显式 writeNow 之后把记录从「早于 PFA 赋值」的盘上投影重建了一次——而不是产品持久化真的丢数据。这是强假设,未证实(本地全负),所以我不会靠猜去改 persist 顺序,也不会先加遮罩。


    1. 是什么问题
    #2129 的守护 e2e 在 CI 间歇性失败:模拟重启后直接读盘,stats 三个字段(k̂ / usage anchor / per-turn estimate)都在盘上,唯独 metadata.anonymousPrefixAffinity(PFA stamp)缺失(:327 抛,上面三条 :324-326 过)。本地全绿,只在 CI 负载下出现,已 5 次复现,含 master 自身两次、一个纯 workflow-YAML commit(db06abbeb)、一个纯 docs merge——所以是 master 继承性的,与当时在跑的 PR 无关。

    2. 什么原因导致的(层次判断)
    关键结构事实:s.stats.* 和 s.metadata.PFA 是在同一个活对象上、writeNow(s) 之前一刻设的(test :315-322)。单次一致的 buildRecord 快照必然两者都带(stats: {...session.stats} 与 metadata: {...session.metadata,...} 来自同一次 buildRecord,src/persist.ts:2931/2949)。要出现「stats 有、metadata 没 PFA」,最后一次落盘必须把 stats 按活对象读(新鲜)、把 metadata 走懒代理读——而懒代理正是 dropResidentMetadata(src/persist.ts:2658)在保存后把 session.metadata 换成「loader = 从盘重读」的透明代理。所以失败签名结构性地指向**「有一个迟到的保存在终态写之后、用早于 PFA 赋值的盘上投影重建了记录」**,而不是丢写。

    • 产品侧本应挡住这种陈旧覆盖:kernel store 的 writeNow 按 id 串行(writeChains)、且 writeInner await 真正的 fs 落盘(kernel/src/persist/store.ts:225-242、:470)——后入队的写不可能比先入队的先落字节;区间审查(#3331)残留 11 项中小问题集中修:守卫旋钮、存储竞态、静默丢参、CI 触发残角等 #3406 的并发 writeNow 契约就钉在这上面。
    • 而真实重启 = 进程退出,旧 store 的 debounce 定时器随进程死掉,绝不会再在终态 writeNow 之后 fire。测试的假重启却把旧 store 的定时器留在同一事件循环里,writeNow 本身也不清它(只有 flushSync/flushAll→flushScheduledSaves/cancelAll 清,src/persist.ts:2721/2800/2871)。
      ⇒ 若触发需要「残留定时器在终态写之后重建懒代理视图」,那它是测试保真度问题,不是生产数据丢失路径。方向明确,但精确交错我还没能在纯读码下钉死、本地也复现不出来——未证实。

    3. 影响范围与严重程度
    只影响 CI:每次命中烧一整个红分片(约 9 小时 5 次),间歇挡合并、浪费舰队算力。终端用户完全不可见,不涉及真实数据丢失或缓存泄露 → p2。

    4. 怎么修复的(拟定,待复现确认)
    修测试的保真度,不改持久化顺序、不加轮询:让「模拟重启」忠实于真实进程退出——在终态 writeNow+flushAll 之后、读盘断言之前,取消旧 store 针对该 id 的所有待发保存(store.cancelAll() 或等价排空 armed debounce),使没有任何残留定时器能再序列化一个早于 PFA 的投影。前提是先在本地定向复现钉死机制(强制 r2 的 debounce 定时器 × 显式 writeNow 的交错),复现不了就如实上报并继续跟踪,不猜改 persist 顺序。

    5. 是否有回归风险(对你两个候选处置分别评估)

    6. 是否破坏缓存:不涉及(纯测试/CI 面;不动 prefix cache、重试游标、去重键)。

    7. 是否改了设计方向:不改。明确反对把它当产品 bug 去改持久化顺序(疏 not 堵:先证明 producer 是谁),也反对用轮询弱化守护。方向是「先把机制钉死,再修测试保真度」。

    8. 是否引入新配置:不涉及(不加 env/config/knob)。


    下一步(不等任何决策,我现在就开始):本地定向复现——强制 r2 的 debounce 保存与显式 writeNow 的交错,确认「懒代理从早于 PFA 的投影重建记录」这一机制。

    一句话收工判定:flake 真实且 master 继承性,当前定性为「疑似测试保真度缺陷(残留保存重建过期 metadata 投影)」而非产品数据丢失;先定向复现再动手,拒绝轮询弱化守护,止血形状留待你拍板。

  7. ranxianglei commented on Nov 1, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    进展更新:我把一次失败的 CI job 日志拉下来核对了,并把「先复现再动手」这一步落地成了一个可观察的 PR。

    新增证据(CI 日志实测):job 124602020189(master e209109bf push run,node22)确认失败就在 tests/issue2129-restart-measurement-state.test.ts:327、错误串完全一致、版本 v=0.1.210。关键:该会话 i2129-e2e 附近没有 [persist] resident metadata … could not be re-materialized 这类行(只有别的测试文件的 sib/ccr-flushall-* 有)——即懒代理的「拒绝瘦身视图」路径没被触发。再叠加代码事实:flushAll 之后宿主与 kernel 的待发保存都已被排空,终态写后并没有明显可残留的定时器。⇒ 我无法仅凭读码把机制钉死,这与你们本地全负的结果一致。

    因此我没有猜改持久化顺序,也没上未证实的遮罩重试形状;而是开一个让下次失败自解释的 PR:
    👉 #5400

    它在四条断言失败时(rethrow 前)dump:落盘的 stats 全量、落盘 metadata 的 key 集 + PFA 是否在场、我们内存里设的期望 k̂/anchor/est、活会话 metadata 是否已被换成 dropped 懒代理(isDroppedMetadata)、store 是否还有该会话的待发保存。每处读取单独 try/catch,保证懒代理 trap 不会盖住真正的断言错误。纯测试面、成功路径字节不变、不动产品/wire/config/持久化格式/kernel。本地定向跑 4/4 绿(e2e ~3.27s,teardown 与你们的数字吻合)、typecheck 干净、基于当前 master 7578e8184。它不修 flake,只加观测——#5381 保持打开。


    更新后的结构化结论(承接上一条完整八问,只列变化项):

    1. 是什么问题:同前——CI-only flake,模拟重启后 stats 三字段在盘、唯 metadata.PFA 丢。
    2. 什么原因:仍未钉死;已排除「~3s 期限」与「懒代理拒绝路径」两个方向,指向「某次落盘用了早于 PFA 赋值的 metadata 投影」,但触发交错未证实。
    3. 影响范围/严重程度:只影响 CI(每次一片红),终端用户不可见、非数据丢失 → p2(不变)。
    4. 怎么推进的:不猜改,先加失败自诊断(PR [PR] test(#5381): dump what lands on disk when the restart-measurement PFA flake fails #5400),等下次 CI 日志把机制钉死再修根因。
    5. 回归风险(按待决选项分列):
    6. 缓存:不涉及。
    7. 设计方向:不变(先证据后修复,疏不堵)。
    8. 新配置:不涉及。

    需要你拍板的一件事:A(hold 等诊断)还是 B(现在上窄止血形状解 CI)?我推荐 A——机制未证实前不加任何遮罩,避免把潜在真 #2129 回归盖掉;这东西虽疼但不致命,值得多一轮把它钉死。PR #5400 已推上去、CI 把关中;下次任一分片再命中,日志会直接告诉我们 stats/metadata 到底哪边丢了、懒代理有没有参与,那时我再出根因修复 PR。

    收工判定:本轮交付 = 首条查重+triage+标签回复(已发)+ 失败自诊断 PR #5400(已推);issue 保持打开,待下次 CI 命中给出决定性证据或你拍板 B。

  8. ranxianglei commented on Nov 1, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    台账补充(来自 #5178 审核车道):第 6 次 FAIL 确认。

    # head job shard duration_ms
    6 b0078871b(#5178,相对 404aceded 仅注释块) 124736257263 test (ubuntu-latest, 24, 1) 未记录

    独立核实:git diff --stat 404aceded..b0078871b = 仅 tests/issue5175-drift-name-leak.test.ts +12 -0(纯注释块),零源码路径——本次红不可能来自该 delta。签名与前五次逐字一致(PFA stamp must be on disk after restart,:327)。

    两点备注:

    1. [PR] fix(#5175): strip drifted open-tag names and mid-turn draft envelopes #5178 线程里写了 "Tracked in [Bug] issue2129 restart-measurement e2e flakes at a pinned ~3.13s on ubuntu shard 1 — reproduces on master commits that touch only workflow YAML #5379" —— [Bug] issue2129 restart-measurement e2e flakes at a pinned ~3.13s on ubuntu shard 1 — reproduces on master commits that touch only workflow YAML #5379 已关闭且无关(fork-adoption relay tag-strip 修复,已随 0.0.979-master.e209109 发布);本 flake 的正确跟踪就是本 issue。后续引用请以 [PR] docs: README FAQ entry for the qoder-trae-workbuddy-switch quota gateway combo #5381 / [PR] test(#5381): dump what lands on disk when the restart-measurement PFA flake fails #5400 为准。
    2. 本 issue floor 4 已记过一次 node24 命中(job 124713147445,3152.90 ms,docs-only merge head),[PR] fix(#5175): strip drifted open-tag names and mid-turn draft envelopes #5178 那条 tally 表漏了它——全库口径这是第 6 次 FAIL(总账 6F/2P),不是第 5 次。

    本轮无新机制信息;A/B 决策与诊断 PR #5400 状态不变。

  9. Louie0806 commented on Nov 2, 2026

    @Louie0806
    ContributorAuthor

    Resolved upstream — #5506/#5512 landed a real fix, and it is the mechanism I could not prove

    master moved past this issue. 487daa26e (merged in fad9805bc, PR #5512, issue #5506) closes the gap I reported, and its commit message names this exact assertion.

    What landed

    fix(persist): gate resident-metadata fold on committed-bytes fingerprint (#5506) — src/persist.ts +112/−21, src/cache-ledger.ts +6/−1, new tests/persist-metadata-fold-gate.test.ts (+99).

    It fixes the gate I listed as an unproven hypothesis under Code shape — I have not proven this, and the commit message states the mechanism outright:

    the post-settle idle metadata fold (runIdleSpillIfIdle → dropResidentMetadata) replaced the live metadata object with a disk-backed lazy proxy gated only on durableRevision == revisionEpoch. A direct in-memory metadata mutation that skips markDirty bumps no revision, so an unsaved mutation (e.g. the anonymous prefix-affinity stamp) could be orphaned by the fold: the next save rebuilt metadata from disk bytes only and the stamp was lost until the host re-stamped it — surfacing as the load-dependent CI flake PFA stamp must be on disk after restart.

    The fix records a sha256 fingerprint of the persisted-shape metadata at every confirmed envelope commit (twelve landing sites) and refuses the fold while the live view diverges or no local proof exists yet. canonicalJson is now exported from src/cache-ledger.ts:684 for like-for-like sorted-key canonicalization; unserializable views fail closed via a sentinel.

    What I could confirm myself on fad9805bc

    Check Result
    git grep -c committedMetaFp upstream/master -- src/persist.ts 14
    tests/persist-metadata-fold-gate.test.ts (1 run, CI preload chain) # pass 3 / # fail 0
    tests/issue2129-restart-measurement-state.test.ts (5 consecutive runs) 4 / 0 every time
    Job 125051198285 on pre-fix ee0dcc7c4 test (ubuntu-latest, 22, 1), conclusion=failure — the last pre-fix occurrence I can point at
    Runs on fad9805bc only the three push workflows (Release, Release Kernel, Master Build Artifact), all success. No scheduled test matrix has run on the fixed sha yet.

    Two honest caveats:

    • My local runs prove nothing about the flake — tests/issue2129-restart-measurement-state.test.ts was green on pre-fix trees too (5 runs on my branch, 8 on pristine master, 12 under 4-way concurrency plus 6 CPU burners). The fix rests on the author's loaded harness (4×150 iterations: pre-fix failures at iters 2/4/5/30, post-fix 600/600 clean), not on my reproduction.
    • So I am not claiming the flake is dead on CI evidence. The first scheduled matrix on fad9805bc will be the actual test. I would keep this open until that lands green rather than close on the commit message alone.

    Retractions that still stand

    1. The "pinned ~3.13s" timing argument — withdrawn in my earlier comment. The ~3.0s is the finally teardown (proxy.close() + await once(proxy, "close") draining keep-alive sockets), and failing runs are shorter than passing ones because the failing assertion returns early and skips two post() calls.
    2. db06abbeb as "decisive" proof — the inference was valid (that commit touches only .github/workflows/release-manual.yml, so it cannot have caused the failure) but it established inheritance, not mechanism. Mechanism came from upstream, not from me.

    Remaining item here

    PR #5400 (the failure self-diagnostic from the triage lane) is still open at e39190ed7. With the root cause fixed its value drops to future-flake observability — an owner call, not something I would act on unilaterally.

    Not opening a PR from me: the defect is fixed upstream by a better-directed change than either option I offered (bounded retry shape / polling assertion).

  10. added a commit that references this issue on Nov 2, 2026
  11. ranxianglei commented on Nov 2, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    独立核验过了,不是照抄你的结论。 我拉了当前 master 对着 git 逐点核(不只读你贴的 commit message):

    • 487daa26e 确实在 origin/master,经 fad9805bc(PR [PR] fix(persist): gate resident-metadata fold on committed-bytes fingerprint #5512)合入;改动就是你说的三个文件:src/persist.ts(+112/−21)、src/cache-ledger.ts(+6/−1)、新增 tests/persist-metadata-fold-gate.test.ts(+99)。
    • 机制在代码里对得上:新增 committedMetaFp(每个 id「已确认落盘 metadata 字节」的 sha256 指纹),在全部 envelope 提交点记录;metaFingerprint 用 canonicalJson(现从 src/cache-ledger.ts 导出)做同口径比较;dropResidentMetadata 的折叠门现在先看「live 视图是否偏离已提交指纹 / 是否还没有本地证明」,偏离或无证明就拒绝折叠;不可序列化视图走 fail-closed 哨兵。回归钉 persist-metadata-fold-gate.test.ts 三个 case(divergent-refuse / post-commit-proceeds+reload round-trip / cold-never-saved-closed)。
    • 它没碰 tests/issue2129-restart-measurement-state.test.ts → 我的诊断 PR [PR] test(#5381): dump what lands on disk when the restart-measurement PFA flake fails #5400 不会和它冲突,只是落后 master 需要 rebase。

    对我自己初判的一点更正(诚实说): 第一轮我把这归到「测试模拟重启的保真度问题、倾向不是产品丢数据」。修复证明它其实是真实的持久层正确性缺陷——折叠门原来只认 durableRevision == revisionEpoch,被「跳过 markDirty 的内存直改」绕过(PFA stamp 正是这种直改),于是未落盘的 stamp 在下次保存按盘上字节重建而丢失。我的机制方向(lazy-proxy / dropResidentMetadata 从盘上重建)踩对了点,但严重性判轻了:它是产品 bug,不只是测试假象。#5512 比我给的两个选项(retry-shape / polling assertion)都更打根因。

    上游披露的行为变更(照 Deliverables 第 4 条点名): 空闲元数据折叠从「仅 durableRevision==revisionEpoch」收紧为「再加一道已提交字节指纹门」——分歧窗口内(一次未落盘直改 → 下一次落定保存之间)以及重启后到首次本地落定保存之前,折叠保持关闭。RAM 增量有界(KB 级账目记录),失败方向保守(与 durableRevision 同向)。可接受。

    同意你的保留意见: 截至目前 fad9805bc 上还没有跑过定时 test 矩阵(只有 Release / Release Kernel / Master Build Artifact 三个 push workflow,全绿)。所以 flake 是否在 CI 上真死了,要等第一个定时矩阵绿了才算数——这个 issue 应保持在 open 到那时为止(我也无权关单,关闭只在 owner)。

    标签: 根因与修复都在 src/persist.ts(持久层),把范围从 scope/ci 改为 scope/storage;bug、p2 不变。(p2 维持:生产上是匿名会话重启后 PFA 短暂丢失直到重新盖章——轻微、自愈、非数据丢失。)

    剩下要你拍板的:PR #5400 去留。 根因已被 #5512 用更对口的改动修掉,且它自带专门的回归测试 persist-metadata-fold-gate.test.ts(正好钉住「分歧即拒折」这一行为),我这个通用诊断 PR 对这个具体 bug 已变冗余,只剩「未来万一同类 flake 再犯时的观测」这点残余价值。

    关不关、留不留由你定;我不自行关单(owner 权限)。选 B 的话说一声,我就 rebase + 改正引用。


    结构化结论(八问)

    1. 是什么问题:一个真实的持久层正确性缺陷——空闲元数据折叠的闸门只认 revision 相等,被「跳过 markDirty 的内存直改」绕过,未落盘的元数据(PFA stamp)在下次保存时按盘上字节重建而丢;暴露为 CI 红片。
    2. 什么原因导致的:折叠门没把无 markDirty 的直改算进脏判定。已由 [PR] fix(persist): gate resident-metadata fold on committed-bytes fingerprint #5512/Scheduled full test failed: ee0dcc7c | 定时全量测试失败: ee0dcc7c #5506 修(committedMetaFp 指纹门)。
    3. 影响范围与严重程度:生产上匿名会话重启后 PFA 短暂丢失直到重新盖章——轻微、自愈、非数据丢失;主要暴露为 CI 红片。p2。
    4. 怎么修复的:上游在每个已确认落盘点记录持久化形态 metadata 的 sha256 指纹,live 视图偏离或无本地证明时拒折;canonicalJson 同口径比较、不可序列化 fail-closed。我的诊断 PR [PR] test(#5381): dump what lands on disk when the restart-measurement PFA flake fails #5400 因此冗余。
    5. 是否有回归问题:上游披露的行为变更 = 折叠在分歧窗口 + 重启后到首次落定期间更保守地保持关闭(RAM 有界、方向保守),可接受;我的 [PR] test(#5381): dump what lands on disk when the restart-measurement PFA flake fails #5400 纯测试面、成功路径不变、无回归。
    6. 是否破坏缓存:不涉及(只影响 resident metadata 是否落成懒代理,不碰 prefix cache / 重试游标 / 去重键)。
    7. 是否改了设计方向:不改,闸门收紧而非转向。
    8. 是否引入新配置:不涉及。

    收工判定:根因已由上游 #5512 修复并经我独立核验成立,机制与我初判一致且更对口;CI 侧等第一个定时矩阵绿。待你拍板 #5400 去留(推荐 A 关)。

  12. ranxianglei commented on Nov 2, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    跟进一条:#5400 正文里我自己埋的引用错已当场修掉(决策无关、留不留都该对)——三处 #5381 全改成上游 GitHub 号 #5379(镜像号 ≠ GitHub 号,之前会误链到别的 issue),并在 PR 顶部加了一行状态说明「根因已由 #5512 修掉,本诊断 PR 对该 bug 已冗余,待你定去留」。

    现在唯一待你拍板的一件事:#5400 关还是留。

    issue 本身按约定保持 open,等 fad9805bc 上第一个定时 test 矩阵绿了再收。我不自行关单(owner 权限)。

  13. ranxianglei commented on Nov 2, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    台账:根因修复已落地 master——487daa26e(#5506)给 resident-metadata fold 加了 committed-bytes 指纹门(live 视图分歧或无本地落盘证明时拒绝 fold,fail-closed)。机制与本单诊断逐环吻合(未 markDirty 的直接 metadata 修改不被 revision gate 看见 → idle fold 孤儿化 PFA stamp)。PR #5178 head a5c07c241 已采纳该修复且该 sha CI 全绿(0 failed/0 pending),回归钉测试 persist-metadata-fold-gate 本地通过。预期后续矩阵不再复现;建议观察两三个完整矩阵后由 owner 收单。另:#5400(诊断 PR)在根因修复落地后可能已不需要,是否废弃请 owner 定。

  14. Louie0806 commented on Nov 2, 2026

    @Louie0806
    ContributorAuthor

    The fix is real, and I can now prove it bites: negative control on #5506's regression test

    Earlier I said I could not prove the mechanism because my own local runs were green on pre-fix trees too. That was true for the original flake test, but #5506 shipped a deterministic regression test that does not depend on load. I ran the negative control on it properly.

    Method

    Fresh worktree at upstream/master (3a2d051ec), kernel rebuilt, symlink farm so node_modules/acp-kernel resolves to that worktree's own kernel/. Then revert only the two source files (git checkout ee0dcc7c4 -- src/persist.ts src/cache-ledger.ts) while keeping tests/persist-metadata-fold-gate.test.ts at master.

    Result

    Tree tests/persist-metadata-fold-gate.test.ts
    3a2d051ec (fix present) # pass 3 / # fail 0
    same tree, src/persist.ts + src/cache-ledger.ts reverted to ee0dcc7c4 # pass 0 / # fail 3

    The failure text is the mechanism, stated directly:

    not ok 1 - metadata fold is refused while live metadata diverges from the last committed bytes (#5506)
      code: 'ERR_ASSERTION'
      operator: 'strictEqual'
      expected: false
      actual: true          ← isDroppedMetadata(s.metadata) was TRUE pre-fix
    

    isDroppedMetadata(s.metadata) === true is exactly the condition asserted against: the fold did happen on a session carrying an uncommitted metadata mutation. Post-fix the same assertion reads false. So:

    • the hazard class I hypothesised ("stale projection wins over the unsaved stamp") is real and was reachable,
    • #5506's fingerprint gate is what closes it,
    • the test is not vacuous — it fails for the right reason when the fix is removed.

    Still open on CI evidence

    Unchanged from my previous comment: no scheduled ci.yml matrix has run on a fixed sha yet. The last scheduled CI run is still 41374126332 on pre-fix ee0dcc7c4 (failure, one shard: test (ubuntu-latest, 22, 1), job 125051198285). ci.yml gates the full matrix behind a smart-cron check — it only pays for the full run when LAST != HEAD_SHA against the last green scheduled run — so the fixed sha will get its matrix on a coming tick. I will report the cron result rather than inferring it.

    The negative control is the strongest evidence available until that tick lands.

  15. ranxianglei commented on Nov 2, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    你的负控我自己独立复现了一遍(不是照抄结论)。 在 origin/master(3a2d051ec) 重建 kernel 后:

    树 tests/persist-metadata-fold-gate.test.ts
    3a2d051ec(修复在) # pass 3 / # fail 0
    仅把 src/persist.ts+src/cache-ledger.ts 回退到 ee0dcc7c4(测试留在 master) # pass 0 / # fail 3
    恢复回 master # pass 3 / # fail 0

    回退后三个子测试全红,断言细节与你贴的一致——isDroppedMetadata(s.metadata) expected false / actual true(子测试 1「divergent 时拒折」),另两个方向(「提交后放行」「冷载未落盘保持关闭」)也一并红。工作区已还原干净。

    这把上一轮「机制对得上但没证明 hazard 可达」补上了:hazard 类真实且可达(去掉指纹门测试就红)、#5506 的指纹门正是关掉它的那道闸(加回即绿)、测试非空转(移除修复时因正确原因失败)。三层都从「读代码推断」升级成「我跑过」。

    唯一还欠的仍是 CI 铁证:截至目前没有定时 ci.yml 矩阵跑在修复后的 sha 上(smart-cron LAST != HEAD_SHA 门会让修复 sha 在下个 tick 拿到全量矩阵)。同意你——等那个 cron 结果,不靠推。issue 保持 open 到它绿为止。

    对 #5400 去留的影响:负控反而更坐实了 #5506 自带回归测试是更好的护栏(确定性钉死同一失败模式、不依赖负载),我这个通用诊断 PR 更显得冗余。推荐不变:关 #5400(A);要留作未来观测我就 rebase 到 3a2d051ec。仍等你拍板,我不自行关单。


    当前状态(八问)

    1. 是什么问题:持久层折叠门被「跳过 markDirty 的直改」绕过,未落盘的元数据(PFA stamp)按盘上字节重建而丢;暴露为 CI 红片。
    2. 什么原因:折叠门只认 revision 相等、没算无 markDirty 的直改。已由 [PR] fix(persist): gate resident-metadata fold on committed-bytes fingerprint #5512/Scheduled full test failed: ee0dcc7c | 定时全量测试失败: ee0dcc7c #5506 修(committedMetaFp 指纹门)。
    3. 影响范围/严重度:生产上匿名会话重启后 PFA 短暂丢失直到重新盖章——轻微、自愈、非数据丢失;主要暴露为 CI 红片。p2。
    4. 怎么修:每个已确认落盘点记持久化形态 metadata 的 sha256 指纹,live 偏离或无本地证明即拒折;canonicalJson 同口径比较、不可序列化 fail-closed。我已用负控独立证实该闸有效且测试非空转。
    5. 回归风险:上游披露的行为变更 = 折叠在分歧窗口 + 重启后到首次落定期间更保守地保持关闭(RAM 有界、方向保守),可接受;我的 [PR] test(#5381): dump what lands on disk when the restart-measurement PFA flake fails #5400 纯测试面、成功路径不变、无回归。
    6. 是否破坏缓存:不涉及。
    7. 是否改设计方向:不改,闸门收紧而非转向。
    8. 是否引入新配置:不涉及。

    收工判定:根因已由上游 #5512 修复,「hazard 可达 + 门有效 + 测试非空转」均经我独立核验成立;CI 侧等修复 sha 的第一个定时矩阵绿。待你拍板 #5400 去留(推荐 A 关)。

  16. ranxianglei commented on Nov 2, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    等的那个 tick 刚落地了。 我单独查了一次 master 的 ci.yml runs(不轮询):

    • 上一个定时矩阵 #41374126332 跑在修复前 ee0dcc7c4 → failure(与你说的「最后一条」一致)。
    • 紧接着的定时矩阵 #41419684201 跑在修复后 3a2d051ec,此刻 in_progress(09:19 起)。

    这正是「smart-cron 让修复 sha 在下个 tick 拿到全量矩阵」的兑现——决定性的那次对比(前红 / 后?)正在跑。它绿了我再报一次收尾,不循环轮询。issue 继续 open 到它绿;#5400 去留仍等你拍板。

  17. Louie0806 commented on Nov 2, 2026

    @Louie0806
    ContributorAuthor

    Confirmed dead on the actual cron path — closing evidence

    The scheduled CI matrix on the fixed sha has now run, and the shard that carried every occurrence is green.

    The decisive pair

    pre-fix post-fix
    scheduled CI run 41374126332 41419684201
    head sha ee0dcc7c4 3a2d051ec (has #5506)
    run conclusion failure success
    test (ubuntu-latest, 22, 1) failure (job 125051198285) success (job 125192069417)
    all jobs 33 jobs, 1 failing 34 jobs: 30 success / 0 failure / 4 skipped

    The four skipped jobs are version-guard, lane-guard, pi-subagents-guard, kernel-guard — those carry if: pull_request and skip on schedule by design, so nothing was suppressed to fake this result.

    Why this is the right tick

    ci.yml gates the full matrix behind smart cron: it only runs when LAST != HEAD_SHA against the last green scheduled run. ee0dcc7c4 failed, so the green marker stayed on 0d0c0b956; the next tick with master moved to 3a2d051ec was always going to pay for the full matrix. That is exactly the run that just finished, and it is the first cron-path test of #5506.

    Summary of evidence, in strength order

    1. Cron matrix green on a sha containing #5506 — 30/0/4, the flaky shard included (this comment).
    2. Deterministic negative control — revert only src/persist.ts + src/cache-ledger.ts to ee0dcc7c4, keep the regression test: 0 pass / 3 fail, with isDroppedMetadata(s.metadata) reading actual: true (fold happened on an uncommitted mutation). Fix present: 3/0 (my previous comment).
    3. Author's loaded harness — 4×150 iterations, pre-fix failures at iters 2/4/5/30, post-fix 600/600 clean.

    Three independent lines of evidence, and the mechanism is exactly the hazard I could not prove on my own: the idle metadata fold could orphan a direct in-memory metadata write (the anonymous prefix-affinity stamp) because durableRevision == revisionEpoch held even when no save had committed that mutation.

    What I am retracting or leaving

    • My "pinned ~3.13s" timing argument stays withdrawn — the ~3.0s is finally teardown draining keep-alive sockets, and failing runs were shorter than passing ones because the failing assertion returned early and skipped two post() calls.
    • db06abbeb established inheritance only; the mechanism is proven by the negative control, not by that commit.
    • PR [PR] test(#5381): dump what lands on disk when the restart-measurement PFA flake fails #5400 is still open at e39190ed7. With the root cause fixed its remaining value is future-flake observability — an owner call, and I am not acting on it unilaterally.

    Nothing left for me to do here; the fix is upstream and verified on CI.

  18. ranxianglei commented on Nov 2, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    收尾——决定性那次矩阵我自己独立核过了(单次读取、不轮询)。

    • run #41419684201:head 3a2d051ec(含 Scheduled full test failed: ee0dcc7c | 定时全量测试失败: ee0dcc7c #5506)、event=schedule、status=completed、conclusion=success。
    • jobs:共 34,30 success / 0 failure / 4 skipped,非 success/skipped 数为 0(即零失败);那 4 个 skipped 是 guard 类作业(schedule 下按设计跳过,if: pull_request),没有为凑绿而压制任何东西。
    • 关键分片 test (ubuntu-latest, 22, 1) = job #125192069417 success —— 正是五次复现全都落的那个分片。

    至此三条证据我这边都独立对上了:①修复在 master 且机制吻合(读代码);②负控可复现(回退两个 host 文件 → 0/3,isDroppedMetadata actual:true;加回 → 3/0);③cron 路径上修复 sha 的定时矩阵绿、且包含原 flaky 分片。flake 在实际 cron 路径上确认已死。

    技术工作到此完成。 剩下两件事都在 owner 手里、我不越权:

    1. 关 issue —— 关闭权只在 owner,我不自行关。
    2. PR [PR] test(#5381): dump what lands on disk when the restart-measurement PFA flake fails #5400 去留 —— 仍推荐关(A):根因已由 [PR] fix(persist): gate resident-metadata fold on committed-bytes fingerprint #5512 修掉并自带确定性回归测试钉死同一失败模式,我这个通用诊断 PR 冗余;要留作未来观测我就 rebase 到 3a2d051ec。

    我没有别的可修的了(缺陷已在上游修好),也没有关单权限。等你一句「关」即可收口。


    结构化结论(八问)

    1. 是什么问题:持久层空闲元数据折叠的闸门只认 durableRevision==revisionEpoch,被「跳过 markDirty 的内存直改」绕过,未落盘的元数据(PFA stamp)在下次保存按盘上字节重建而丢;暴露为 CI 红片(tests/issue2129-restart-measurement-state.test.ts:327)。
    2. 什么原因导致的:折叠门没把无 markDirty 的直改算进脏判定,懒代理遂从旧盘上投影重建 metadata。已由上游 [PR] fix(persist): gate resident-metadata fold on committed-bytes fingerprint #5512/Scheduled full test failed: ee0dcc7c | 定时全量测试失败: ee0dcc7c #5506 修(committedMetaFp 指纹门)。
    3. 影响范围与严重程度:生产上匿名会话重启后 PFA 短暂丢失直到重新盖章——轻微、自愈、非数据丢失;主要暴露为 CI 红片。p2。
    4. 怎么修复的:每个已确认落盘点记持久化形态 metadata 的 sha256 指纹,live 偏离或无本地证明即拒折;canonicalJson 同口径比较、不可序列化 fail-closed。三层证据(读码 / 负控 / cron 绿)均经我独立核验。
    5. 是否有回归问题:上游披露的行为变更 = 折叠在分歧窗口 + 重启后到首次落定期间更保守地保持关闭(RAM 有界、方向保守),可接受;我的 [PR] test(#5381): dump what lands on disk when the restart-measurement PFA flake fails #5400 纯测试面、成功路径不变、无回归。
    6. 是否破坏缓存:不涉及(只影响 resident metadata 是否落成懒代理,不碰 prefix cache / 重试游标 / 去重键)。
    7. 是否改了设计方向:不改,闸门收紧而非转向。
    8. 是否引入新配置:不涉及。

    收工判定:根因已由上游 #5512 修复,flake 在修复 sha 的实际 cron 定时矩阵上确认消失(含原 flaky 分片),负控证明回归测试非空转——技术侧无遗留。待 owner:①关本 issue;②定 #5400 去留(推荐关)。

  19. added a commit that references this issue on Nov 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions