Skip to content

test(plugin-sharing): size the three scrypt cases near vitest's 5000 ms default to their measured work - #22426

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-22418-share-link-scrypt-timeout
Oct 9, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-22418-share-link-scrypt-timeout

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #22418
Clause-②: no

What changes

Three cases in packages/plugins/plugin-sharing get an explicit per-case timeout, sized to the work each one measures. Every other scrypt-reaching case in the package keeps vitest's 5000 ms default; the enumeration below gives each one's reading and the reason.

Case Work (scrypt derivations) Slowest Test Core reading Budget
share-link-password-webcontainer.test.ts:95, "a password whose NFKC form differs from its input is one password on both paths" (the card's case, :72 on ca135dcc) 4 pure-JS + 4 native timed out at 5030 and 5039 ms; 5515 ms projected 17_000
share-link-password-webcontainer.test.ts:74, "a hash minted on WebContainer verifies natively, and the reverse" 3 pure-JS + 3 native 4136 ms 13_000
share-link-password.test.ts:481, "no log line carries the presented password, on any outcome" 9 native 3805 ms 12_000

Spelled the way this repo spells a per-case budget, as the third argument of it: two named constants in the webcontainer file and a literal 12_000 in the other. A comment beside each records the rule, the derivation count and the readings, so the next author can re-size from a fresh reading.

Not touched: the scrypt parameters (N, r, p), any production file, the vitest config, testTimeout anywhere, and the case bodies. No skip, no quarantine, no retry. No changeset: only two *.test.ts files change, and the package's files[] is dist, README.md and CHANGELOG.md.

The rule and the margin

  • Which cases get a budget: a case whose slowest CI reading is over half of the 5000 ms default. The default leaves every other case that has a CI reading at least 2x headroom over its slowest reading, and 2x is above the largest spread one case showed between the two CI runs: 1.88x (/resolve still accepts the ?password= query parameter, 588 ms in one run and 1104 ms in the other).
  • How big: 3x the slowest CI reading, rounded up to the next second. 3x covers that measured 1.88x spread with room for a heavier shard. The suspected trigger for these reds, per the card, is a shard re-pack (040184752c, chore(ci): refresh the Test Core shard-timings dataset #22368), which can change a shard's load again without touching this package.
  • The NFKC case's reading is projected, because CI cut it off at the timeout. Its derivations are 4/3 of the cross-verify case's (4 + 4 against 3 + 3), and the cross-verify case read 4136 ms in the same run, so the projection is 5515 ms. The 4/3 ratio checks out locally: the two cases read 926 and 675 ms idle (1.37x), and 5557 and 4682 ms under contention (1.19x). Under contention the projection overestimates, so it errs toward a larger budget.

Readings

CI (per-case durations from vitest's default reporter in the two red Test Core (5/6) job logs on the card, read through the job-log API): job 113665992253 (push, e75dceddd7) and job 113668129564 (PR #22215 merge of e75dceddd7). The log API returns only the last 5000 lines of a 19k to 48k line log. In both red jobs, plugin-sharing's last 24 files fall in that window, and the two share-link-eligibility.test.ts cases do not. Three hourly scheduled Test Core (5/6) jobs (113678118904 on ca135dcc, 113663806311, 113649948997) and the two green jobs on the card had no plugin-sharing lines in their windows. The direct REST log download is refused by this container's egress proxy, so no other log was reachable.

Local (a 4-vCPU container; every timing run was taken under the shared verify lock, before the gate battery started, with no other run alongside; the load average was 0.2 when the work began). The census and idle readings use --maxWorkers=2. The contention runs pin the four scrypt-reaching files to one core beside 5 busy loops on that core, with --maxWorkers=1. That load was picked because the NFKC case's CI-to-local ratio is at least 5.4x (at least 5030 ms against 926 ms). Case by case, it reads 0.95x to 1.48x the slowest CI reading, across the 22 cases that read over 1 s in CI and passed under it. So it is a load as heavy as the red shard, or heavier.

Case Contention, before (ca135dcc) Contention, after (616794ce1) Idle, before Idle, after
NFKC (webcontainer :95) Test timed out in 5000ms (5024 ms) passed, 5557 ms 958 ms 833 ms
cross-verify (webcontainer :74) passed, 3935 ms passed, 4682 ms 675 ms 681 ms
no-log (share-link-password.test.ts:481) Test timed out in 5000ms (5067 ms) passed, 5016 ms 789 ms 789 ms
whole contention run, 4 files 2 failed, 94 passed (96) 96 passed (96)
idle run, the two named files 48 passed (48) 48 passed (48)

The before contention run reproduces the card's failure shape: the same case, as a timeout and not an assertion. It also shows the no-log case is the next one to go.

The enumeration pin: every case in the package that runs a scrypt-class derivation

The only scrypt-class key derivation in the package is share-link-password.ts: deriveKeyNode (node:crypto scrypt) and deriveKeyPureJs (@noble/hashes scrypt). A test reaches them through hashShareLinkPassword / verifyShareLinkPassword, through ShareLinkService.createLink with a password, through resolveToken against a stored scrypt$ hash, or through the legacy upgrade.

The list is measured, not grepped: a temporary vitest setup file, never committed, wrapped node:crypto's scrypt and scryptSync, synced them into the builtin ESM namespace, and counted the calls per case. It ran over the whole package at ca135dcc: 40 files, 1002 cases, 1002 census rows. 52 cases in 4 files reach a derivation:

  • share-link-password-webcontainer.test.ts: 4 cases;
  • share-link-password.test.ts: 42 of its 44 cases. The two others are the injected-hasher case and the authenticated list-route case;
  • share-link-internal-columns.test.ts: 4 cases;
  • share-link-eligibility.test.ts: 2 cases.

Pure-JS derivations cannot be counted that way, because @noble/hashes is a native ESM namespace. They are counted by reading the four webcontainer cases, which are the only cases in the package that set a WebContainer signal (a grep for STACKBLITZ, /bin/jsh and webcontainer across the package's tests finds no other).

"Local, idle" is the census run. "CI" is the push / PR red run.

File:line at HEAD Case scrypt work Local, idle (ms) CI (ms), push / PR Budget
share-link-password-webcontainer.test.ts:95 a password whose NFKC form differs from its input is one password on both paths 4 pure-JS + 4 native 926 timed out at 5030 / 5039 17_000 ms = 3 × 5515 (projected), rounded up
share-link-password-webcontainer.test.ts:74 a hash minted on WebContainer verifies natively, and the reverse 3 pure-JS + 3 native 675 4136 / 3688 13_000 ms = 3 × 4136, rounded up
share-link-password-webcontainer.test.ts:121 the pure-JS key is byte-identical to node:crypto for the same password and salt 1 pure-JS + 1 native 216 1435 / 1137 default: 29% of 5000 ms
share-link-password-webcontainer.test.ts:64 the WebContainer path does not touch node:crypto.scrypt; the native path does 1 pure-JS + 1 native 270 1305 / 1393 default: 28% of 5000 ms
share-link-password.test.ts:481 no log line carries the presented password, on any outcome 9 native 793 3805 / 3417 12_000 ms = 3 × 3805, rounded up
share-link-password.test.ts:352 legacy plaintext row › still verifies, refuses a wrong password without touching the row, and is upgraded by the right one 5 native 584 2163 / 1342 default: 43% of 5000 ms
share-link-password.test.ts:352 legacy salted SHA-256 row › still verifies, refuses a wrong password without touching the row, and is upgraded by the right one 5 native 731 2132 / 1815 default: 43% of 5000 ms
share-link-password.test.ts:617 /resolve sends no-store + Vary on success and on every refusal 3 native 280 1573 / 938 default: 31% of 5000 ms
share-link-password.test.ts:338 the verifier accepts the right password and refuses a wrong one, an empty one and an unknown form 4 native 332 1407 / 1356 default: 28% of 5000 ms
share-link-password.test.ts:284 the list and the redemption result, through the service and through the route, carry no hash 3 native 291 1387 / 951 default: 28% of 5000 ms
share-link-password.test.ts:617 /messages sends no-store + Vary on success and on every refusal 3 native 289 1337 / 942 default: 27% of 5000 ms
share-link-password.test.ts:582 /resolve never falls back to a raw compare: an undecodable value that IS the raw password is still refused 2 native 212 1244 / 992 default: 25% of 5000 ms
share-link-password.test.ts:391 a refused upgrade write does not block the read, leaves the legacy form verifying, and is reported once without the password 3 native 260 900 / 1138 default: 23% of 5000 ms
share-link-password.test.ts:552 /messages still serves a raw with a %-escape password sent raw, with no encoding header 2 native 174 1110 / 896 default: 22% of 5000 ms
share-link-password.test.ts:445 /resolve still accepts the ?password= query parameter 2 native 197 588 / 1104 default: 22% of 5000 ms
share-link-password.test.ts:535 /messages does not guess: the same encoded value with no encoding header is compared raw 2 native 195 1092 / 955 default: 22% of 5000 ms
share-link-password.test.ts:600 /messages: the ?password= form still wins, and the header pair is then not read 2 native 212 1075 / 886 default: 22% of 5000 ms
share-link-password.test.ts:552 /messages still serves a Latin-1 password sent raw, with no encoding header 2 native 187 1072 / 822 default: 21% of 5000 ms
share-link-password.test.ts:523 /messages serves a emoji password sent percent-encoded under utf-8 2 native 196 994 / 1046 default: 21% of 5000 ms
share-link-password.test.ts:600 /resolve: the ?password= form still wins, and the header pair is then not read 2 native 188 1044 / 828 default: 21% of 5000 ms
share-link-password.test.ts:552 /messages still serves a raw with a stray % password sent raw, with no encoding header 2 native 174 1038 / 951 default: 21% of 5000 ms
share-link-password.test.ts:552 /resolve still serves a raw with a %-escape password sent raw, with no encoding header 2 native 183 938 / 1033 default: 21% of 5000 ms
share-link-password.test.ts:535 /resolve does not guess: the same encoded value with no encoding header is compared raw 2 native 187 988 / 1000 default: 20% of 5000 ms
share-link-password.test.ts:326 a new link stores scrypt, with no plaintext, and two hashes of one password differ 2 native 172 710 / 951 default: 19% of 5000 ms
share-link-password.test.ts:523 /messages serves a CJK password sent percent-encoded under utf-8 2 native 170 878 / 950 default: 19% of 5000 ms
share-link-password.test.ts:433 /resolve accepts the x-share-password header 2 native 179 712 / 945 default: 19% of 5000 ms
share-link-password.test.ts:445 /messages still accepts the ?password= query parameter 2 native 223 943 / 700 default: 19% of 5000 ms
share-link-password.test.ts:552 /resolve still serves a raw with a stray % password sent raw, with no encoding header 2 native 202 937 / 738 default: 19% of 5000 ms
share-link-password.test.ts:460 /resolve refuses a wrong password sent as a header 2 native 196 841 / 913 default: 18% of 5000 ms
share-link-password.test.ts:552 /resolve still serves a Latin-1 password sent raw, with no encoding header 2 native 210 912 / 865 default: 18% of 5000 ms
share-link-password.test.ts:267 the mint response, through the service and through the route, carries no hash 2 native 225 799 / 900 default: 18% of 5000 ms
share-link-password.test.ts:433 /messages accepts the x-share-password header 2 native 206 824 / 897 default: 18% of 5000 ms
share-link-password.test.ts:305 the list and the redemption result carry no hash even from an engine that does not strip it 2 native 191 884 / 895 default: 18% of 5000 ms
share-link-password.test.ts:460 /resolve refuses a wrong password sent as a query 2 native 217 894 / 874 default: 18% of 5000 ms
share-link-password.test.ts:523 /resolve serves a emoji password sent percent-encoded under utf-8 2 native 178 843 / 819 default: 17% of 5000 ms
share-link-password.test.ts:582 /messages never falls back to a raw compare: an undecodable value that IS the raw password is still refused 2 native 221 795 / 834 default: 17% of 5000 ms
share-link-password.test.ts:460 /messages refuses a wrong password sent as a header 2 native 173 773 / 770 default: 15% of 5000 ms
share-link-password.test.ts:460 /messages refuses a wrong password sent as a query 2 native 208 652 / 741 default: 15% of 5000 ms
share-link-password.test.ts:523 /resolve serves a CJK password sent percent-encoded under utf-8 2 native 200 616 / 691 default: 14% of 5000 ms
share-link-password.test.ts:568 /messages refuses an octet that is not UTF-8 with 400 VALIDATION_FAILED, before the token is looked up 1 native 87 574 / 513 default: 11% of 5000 ms
share-link-password.test.ts:568 /resolve refuses an encoding the server does not read with 400 VALIDATION_FAILED, before the token is looked up 1 native 88 550 / 365 default: 11% of 5000 ms
share-link-password.test.ts:568 /resolve refuses an octet that is not UTF-8 with 400 VALIDATION_FAILED, before the token is looked up 1 native 110 538 / 484 default: 11% of 5000 ms
share-link-password.test.ts:568 /messages refuses an encoding the server does not read with 400 VALIDATION_FAILED, before the token is looked up 1 native 104 537 / 450 default: 11% of 5000 ms
share-link-password.test.ts:568 /resolve refuses a truncated UTF-8 sequence with 400 VALIDATION_FAILED, before the token is looked up 1 native 86 446 / 497 default: 10% of 5000 ms
share-link-password.test.ts:568 /messages refuses a truncated UTF-8 sequence with 400 VALIDATION_FAILED, before the token is looked up 1 native 92 496 / 455 default: 10% of 5000 ms
share-link-password.test.ts:375 is not attempted for a switched-off object, whose redemption is refused 1 native 96 303 / 439 default: 9% of 5000 ms
share-link-eligibility.test.ts:1191 the password_hash shape — NEEDS_PASSWORD and WRONG_PASSWORD both become the unknown-token answer 5 native 662 NOT MEASURED (outside both logs' tail windows) default (eligibility note below)
share-link-eligibility.test.ts:1291 fail-closed: an object whose schema the engine cannot answer for is refused, not probed 2 native 336 NOT MEASURED (outside both logs' tail windows) default (eligibility note below)
share-link-internal-columns.test.ts:143 a password-protected link refuses without the password and with a wrong one, and opens with it 3 native 318 1361 / 1528 default: 31% of 5000 ms
share-link-internal-columns.test.ts:155 the redemption route answers password-required, refuses a wrong password, and opens with the right one 3 native 295 1489 / 1289 default: 30% of 5000 ms
share-link-internal-columns.test.ts:130 the generic read omits both columns; storage holds both 1 native 150 774 / 566 default: 15% of 5000 ms
share-link-internal-columns.test.ts:182 the creator's link list carries each link's token, and no hash 1 native 95 391 / 416 default: 8% of 5000 ms

The eligibility note. Neither share-link-eligibility.test.ts case has a CI reading, because both finished before either red log's window opened. They keep the default, on two measured comparisons:

  • The same work as the legacy rows. The password_hash-shape case does 5 native derivations, exactly as many as each legacy row, and its idle reading is between theirs: 662 ms against 731 and 584 ms. The legacy rows' slowest CI reading is 2163 ms, 43% of the default.
  • Under the contention load it passed, at 2832 ms (before) and 2902 ms (after), beside the legacy rows at 2714 / 2638 ms and 2800 / 2988 ms in the same runs. That load reads up to 1.48x the CI shard, and the case was still at 58% of the default.

Its CI time is NOT MEASURED, and is stated as such rather than inferred into the table. The fail-closed case does 2 derivations and read 336 ms idle and 1113 ms under contention.

Mechanism assumptions, checked

  1. "Two test files run scrypt-class derivations." Four do. share-link-internal-columns.test.ts (4 cases) and share-link-eligibility.test.ts (2 cases) reach the derivation through ShareLinkService. Neither is anywhere near the budget, so neither is edited, and the file surface stays the two claimed files.
  2. "5030 / 5039 ms for the NFKC case and 1.1 to 4.1 s for its siblings." Confirmed from the same two job logs: siblings 1305 / 1393, 4136 / 3688 and 1435 / 1137 ms. The logs also show a third case near the budget in the other file, the no-log case at 3805 / 3417 ms. The contention run reproduces it as the next timeout.
  3. "Follow test(rest): meta-state-route-engine-outage.test.ts's multi-kernel wiring case times out at vitest's 5000 ms default under the hourly full run, and has turned main's hourly run red three times in two days #21920's timeout spelling and margin comment." The precedent has no per-case timeout. test(rest): meta-state-route-engine-outage.test.ts's multi-kernel wiring case times out at vitest's 5000 ms default under the hourly full run, and has turned main's hourly run red three times in two days #21920 landed as PR test(rest): pay the state route's objectql load at collection, so the multi-kernel case stops timing out on the hourly run #21925 (7b6c65290), which states "No global testTimeout change, no per-case timeout". It moved a module load out of the clocked window into collection, because that cost was loading, not the case's own work. The shape does not transfer here. The cost here is the case's own work, the triage ruled that work stays, and the derivation's only module load (@noble/hashes, on the first pure-JS call) is small: the two one-derivation-per-path cases read 1305 / 1393 and 1435 / 1137 ms in CI. So the spelling follows the repo's own per-case convention instead: the third argument of it, with a comment stating why and how much. packages/drivers/driver-sql's "Why the it() below carries an explicit 60_000 budget" blocks are an example.

Tests and gates

All at 616794ce1 (git rev-parse --short HEAD), the PR head.

  • pnpm --filter '@objectstack/plugin-sharing^...' build (the dependency closure), under the verify lock: VERDICT command-exit 0.

  • pnpm --filter @objectstack/plugin-sharing test, under the lock: Test Files 40 passed (40), Tests 1002 passed (1002), VERDICT command-exit 0.

  • pnpm --filter @objectstack/plugin-sharing typecheck, under the lock: VERDICT command-exit 0, with check:test-typecheck: OK, 3 pre-existing ledgered errors held. tsc -p tsconfig.test.json --listFiles counts 40 src/**/*.test.ts in the program, both edited files among them.

  • The two named files, pnpm --filter @objectstack/plugin-sharing exec vitest run --maxWorkers=2 FILES: 48 passed (48), before and after. Per-case readings are in the table above.

  • The contention reproduction, before and after: shown in the readings table above.

  • Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 57 commands for this diff, and all 57 exit 0. Three needed a second run, which is declared here:

    • check:i18n and check:dual-build-cjs-loads first answered PREREQUISITE NOT MET (exit 3, no dist/). Each was run again after building its prerequisite (the i18n closure the gate names, then the workspace build), and both exit 0.
    • check:type-check-debt first answered exit 3 because its own build collided with my concurrent closure build: plugin-webhooks' DTS step read a half-written service-messaging dist/. Run again alone, it exits 0.
    • --ran reconciliation: "57 derived famil(ies) accounted for — 57 run, 0 NOT-MEASURED (a DERIVED zero — all 57 recorded an exit code and none of them is 3)".
  • Lint, narrowed to the two changed files: eslint --no-inline-config --format json on both files gives 0 errors and 0 warnings. The narrowing loses nothing, for three reasons:

    • both files are inside eslint.config.mjs's population, since an ignored file would come back with an ignore warning;
    • the JSON output counts 2 files;
    • the config never enables type-aware linting (its own comment says so: no parserOptions.project, no typed rules) and loads no import-resolving plugin, so this diff cannot move the verdict on any file it does not touch.

    The full pnpm lint is CI's run.

  • No ablation leg is needed beyond the before/after pair. The "before" leg is the committed ca135dcc tree, which is exactly this change removed, and it was run under the same load as the "after" leg.

Acceptance notes

  • share-link-password.test.ts's header lists, as pinned, "the pure-JS scrypt the WebContainer path uses and node:crypto's produce interchangeable hashes". No case in that file pins it: the pin lives in share-link-password-webcontainer.test.ts. This is a stale doc bullet, an observation only, and not filed. Carrier: none.
  • The job-log API returns only a log's last 5000 lines, and this container's egress proxy refuses the REST log download. So per-case CI readings were reachable only for files that finish late in their shard. That is why the eligibility cases have no CI reading. It is an observation about the measuring instrument, not about the code.
  • Re-sizing: when a budgeted case's derivation count changes, its budget changes by the same ratio, from a fresh CI reading. Lowering N or r to make a case fit is ruled out by the triage on the card.

Generated by Claude Code

…ms default to their measured work

The share-link password cases derive scrypt keys at the production
parameters on purpose, so their wall time scales with the shard that runs
them. Three of them read over half of the 5000 ms default on Test Core:
the NFKC case (timed out at 5030 and 5039 ms), the cross-verify case
(4136 ms) and the no-log case (3805 ms). Each now carries an explicit
budget of 3x its slowest reading, rounded up to the next second. The
other 49 scrypt-reaching cases in the package keep the default.

No parameter, production file or vitest config changes.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYYhVJ78u7PhwFViWo1EmQ
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 27a8b33dec2eb98b73b3e5146e740067cdbd7806 → packageMentionDocs.

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 37893672824 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (5/6) — 失败步骤: Check this shard's timing drift(日志不可读,点进 job 看)

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 8 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 37894053453 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (5/6) — 失败步骤: Check this shard's timing drift(日志不可读,点进 job 看)

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 9 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants