Observed 2026-08-20 while driving PR #10422 (an unrelated plugin-sharing change) to green. Filed unassigned and unrouted — it is not domain:services work; triage should place it.
What happened
Lint & Repo Gates failed on PR #10422 with:
✗ check-query-options-erasure-ratchet: 1 parse failure(s) inside the population this gate measures:
• packages/spec/src/migrations/registry.ts:0:0 — Parsing error: Maximum call stack size exceeded
ESLint returns a parse failure as a message with no rule id, so it matches no
rule this gate counts. […] Nothing was counted this run.
PR #10422's diff is 3 files, none of them in packages/spec (sharing-rule-service.ts, one new test, one changeset). The gate parses its whole population regardless of the diff, so the failing file is not reachable from the change.
Why it is intermittent rather than simply broken
packages/spec/src/migrations/registry.ts is byte-identical on main since f3996182b (16:17, #10252) — 533,578 bytes / 6,861 lines. Four PRs merged after that commit with this same gate green:
91f303cb4 18:56 #10415
965d370d5 18:52 #10421
24ba050f3 18:52 #10423
6439f8b42 18:36 #10411
Same file, same bytes, green then red. So this is not a content defect that someone introduced — it is V8 stack exhaustion while parsing one very large file, and how much stack is available depends on the runner and on what else is executing. That makes it load-dependent and therefore intermittent.
⚠️Not reproduced locally, stated as such. The shared checkout has no installed dependencies and I declined to mutate it to get them, so the paragraph above rests on the CI evidence and the file's measured size, not on a local repro. Someone taking this card should reproduce before choosing a remedy.
Why it is worth a card and not a shrug
The file is an ADR-0087 "forever artifact" — its own header says every step back to MIGRATION_SUPPORT_FLOOR stays replayable, so it only ever grows. Measured across its three most recent commits:
| commit | date | bytes |
|---|
73cfddfa9 | 08-19 14:05 | 528,376 |
3ee8ddf01 | 08-20 13:37 | 533,578 |
f3996182b | 08-20 16:17 | 533,578 |
(9a41e65d3, 08-19: 523,581.)
The margin to ESLint's stack limit shrinks monotonically and by design. Today it is an occasional red that costs a PR a cycle and reads as a flake. It does not stay occasional: each protocol major that carries a break appends another step, and at some size the parse fails on every run — at which point check:query-options-erasure stops measuring its entire population, on every PR, simultaneously.
That last part is the sharp end. The gate's own message says it refuses to count rather than report a file it could not read — which is the right refusal — but the failure mode is all-or-nothing: one unparseable file means nothing was counted this run. So the day this stops being intermittent, the gate goes from "occasionally noisy" to "structurally unable to run", and it will look like a sudden break rather than the arrival of a trend that was visible now.
Recording it as a shrinking-headroom residual rather than an observation: this lane has already paid once this week for treating "it survived, with less margin than expected" as a note instead of a condition (a single test's timing margin jammed the repo's merge queue for 3.5 hours).
Directions (not a recommendation — needs grading by whoever owns the gate)
- Raise the parser's stack for this run (
node --stack-size=… around the ESLint invocation). Cheapest; treats the symptom and buys a bounded amount of time, and the bound is not obvious from the outside. - Split the registry into one module per protocol major with a thin re-exporting index. Keeps the forever-artifact property while capping any single file's parse cost. Costs a generated-output shape change and whatever reads it.
- Exclude the generated file from the ESLint population and cover it by whatever checks generated output already gets. Requires arguing that nothing this gate measures can occur in it — the gate exists precisely to refuse unmeasured files, so this needs to be a positive argument, not an exemption of convenience.
Refs
scripts/check-query-options-erasure-ratchet.mjs · packages/spec/src/migrations/registry.ts · ADR-0087 D3 · PR #10422 (where it surfaced) · run 32404189532
⚠️ Free-text issue search returns total_count: 0 on this repo (measured), so I could not dedupe this against an existing card by content. If one exists, close this as a duplicate.
Observed 2026-08-20 while driving PR #10422 (an unrelated
plugin-sharingchange) to green. Filed unassigned and unrouted — it is notdomain:serviceswork; triage should place it.What happened
Lint & Repo Gatesfailed on PR #10422 with:PR #10422's diff is 3 files, none of them in
packages/spec(sharing-rule-service.ts, one new test, one changeset). The gate parses its whole population regardless of the diff, so the failing file is not reachable from the change.Why it is intermittent rather than simply broken
packages/spec/src/migrations/registry.tsis byte-identical onmainsincef3996182b(16:17, #10252) — 533,578 bytes / 6,861 lines. Four PRs merged after that commit with this same gate green:Same file, same bytes, green then red. So this is not a content defect that someone introduced — it is V8 stack exhaustion while parsing one very large file, and how much stack is available depends on the runner and on what else is executing. That makes it load-dependent and therefore intermittent.
Why it is worth a card and not a shrug
The file is an ADR-0087 "forever artifact" — its own header says every step back to
MIGRATION_SUPPORT_FLOORstays replayable, so it only ever grows. Measured across its three most recent commits:73cfddfa93ee8ddf01f3996182b(
9a41e65d3, 08-19: 523,581.)The margin to ESLint's stack limit shrinks monotonically and by design. Today it is an occasional red that costs a PR a cycle and reads as a flake. It does not stay occasional: each protocol major that carries a break appends another step, and at some size the parse fails on every run — at which point
check:query-options-erasurestops measuring its entire population, on every PR, simultaneously.That last part is the sharp end. The gate's own message says it refuses to count rather than report a file it could not read — which is the right refusal — but the failure mode is all-or-nothing: one unparseable file means nothing was counted this run. So the day this stops being intermittent, the gate goes from "occasionally noisy" to "structurally unable to run", and it will look like a sudden break rather than the arrival of a trend that was visible now.
Recording it as a shrinking-headroom residual rather than an observation: this lane has already paid once this week for treating "it survived, with less margin than expected" as a note instead of a condition (a single test's timing margin jammed the repo's merge queue for 3.5 hours).
Directions (not a recommendation — needs grading by whoever owns the gate)
node --stack-size=…around the ESLint invocation). Cheapest; treats the symptom and buys a bounded amount of time, and the bound is not obvious from the outside.Refs
scripts/check-query-options-erasure-ratchet.mjs·packages/spec/src/migrations/registry.ts· ADR-0087 D3 · PR #10422 (where it surfaced) · run 32404189532total_count: 0on this repo (measured), so I could not dedupe this against an existing card by content. If one exists, close this as a duplicate.