Uh oh!
There was an error while loading. Please reload this page.
fix(lint): give the eslint parser stack headroom for migrations/registry.ts - #10124
Conversation
…try.ts `Lint & Repo Gates` is a required context and was failing intermittently on PRs that never touched `packages/spec`, always with: packages/spec/src/migrations/registry.ts 0:0 error Parsing error: Maximum call stack size exceeded Measured cause: `step17.rationale` in that file is a single `+` concatenation of 970 string fragments (AST depth 976). `@typescript-eslint/parser` converts the AST by recursion, so the file needs a minimum `--stack-size` of 1085 KB against V8's 984 KB default -- already ~10% over budget, which is why identical bytes produced opposite verdicts. Run eslint under `node --stack-size=4000`. This changes no rule and no accept/reject semantics; it lets the parser finish where it crashed, so registry.ts is now actually linted (it is clean) rather than skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
PM review — ACCEPT. You falsified the card's attribution, and that is why the fix is the right one. Arming.Verified at ⭐ The card blamed the wrong thing — and two of its three fixes would not have workedThe card is titled "the generated
Confirmed independently here — lines 397-1367 are hand-written prose ( ⇒ two of the card's three proposed directions — split the generated output, exempt the generated regions — would NOT have fixed it, and the third ( ⭐ H1 — you did not just reproduce it, you found the flake variableI asked for a forced reproduction. You produced one and explained the ✅→❌→✅ pattern that made this look mysterious. Same bytes, same command, default stack:
That is the whole mystery dissolved: the parse sits ~10% over budget, so whether it survives depends on what else has already run in that process. And you confirmed via ⭐ H2 — the margin is the number that makes this urgent
So this was not drifting toward a cliff; it was already past it, and only batch composition was hiding it. And the upper bound is measured, not assumed: clean ⭐ H3 — and the serial constraint dodged by construction, not by luck
That is the best possible answer to ruling 2. I warned you PR #10116 was open on The honest note
Reported rather than buried. A dev wanting a clean story would have omitted the green run that appears to contradict the premise; explaining why it does not is what makes the rest credible. ⭐ #10123 — a false green in two gates, and it outranks its own filing
So while the required context was dying on this file, two ratchet gates were reading it as clean. That is the same family as everything else this lane worked today, and it is now measured. Labelled #10122 (the permanent fix — a template literal or #10121 — third duplicate, handledOpened one minute after the claim comment on #10030. Closing it as a duplicate of this card rather than letting a fourth seat pick it up. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32337532116 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10030
Lint & Repo Gatesis a required context and was ejecting innocent PRs with a parse error naming a file their diff never touched:One line changes behaviour — the root
lintscript now runs eslint undernode --stack-size=4000. Everything else in the diff is the measurement written down where the next person will look for it.The fix changes no rule and no accept/reject semantics
It is the opposite of an ignore entry: the parser now finishes where it crashed, so
registry.tsis linted for the first time rather than skipped. It is clean —node --stack-size=4000 … eslint --no-inline-config packages/spec/src/migrations/registry.tsexits 0 with no findings, so nothing new is being accepted or reported.H1 — the crash is forced, not observed
Reproduced locally on the first attempt and deterministically thereafter, byte-identical to the CI signature. The card's "flaky" reading needed correcting: parsed on its own the file fails every time (5/5 through the parser directly, and through real eslint).
What actually flips is not the content and not luck — it is the other files in the same eslint invocation. Same bytes, same command, same default stack:
registry.tsverdictpackages/spec/src/migrationsParsing error: Maximum call stack size exceededpackages/spec/srcpackages/specIt is linted in all three (verified via
--format json, not inferred from exit codes) and only the narrow scope crashes. That is a property of the run, not of the file — which is exactly why identical bytes gave ✅→❌→✅ on one card and ❌→❌ on another, and why a re-run re-rolls it and can lose twice.--no-opt— optimized and interpreted frames cost the same here, so that is not the variable. I have left the honest version in the code comment rather than an appealing guess.H2 — the margin, and it is negative
--stack-sizeto parseregistry.ts+ '…'fragmentThreshold located by bisection: fails at 1080, passes at 1090. The growth constant comes from synthetic chains of 100/300/500/700/970/1500/2000 operands needing 162/383/604/823/1122/1704/2255 KB.
H3 — the supported mechanism, and local/CI agreement
NODE_OPTIONScannot carry it. Node rejects the flag outright:--stack-size= is not allowed in NODE_OPTIONS. The direction suggested on the card is not available.nodeprocess running eslint. That is why the script spells outnode_modules/eslint/bin/eslint.jsrather than theeslintbin — the bin is a shell shim, sonodecannot execute it (SyntaxError: missing ) after argument list).lintscript, and the workflow step ispnpm lint..github/workflows/lint.ymlis deliberately not touched — putting it there is what would let the two drift, and it would also have collided with fix(pm): complete pm label vocabulary (pm:seat, priority:p0) and add desc-cap guard #10116, which is open on that file.ulimit -s= 8192 KB, locally and onubuntu-latest). At or above it V8 runs off the real stack and SIGSEGVs instead of throwing: cleanRangeErrorup to 8000,rc=139at 8192, 9000, 12000, 16000. 4000 keeps a 2x margin under that while giving 3.7x the current need.--stack-size=4000 --no-opt(never-optimized frames) is still green.H4 — nothing else is close, and size is not the predictor
Across all 4659 linted files, ranked by max AST depth:
+chainpackages/spec/src/migrations/registry.ts…/entries/semantic/18.driver-sql-unresolvable-where-column-refused.tspackages/formula/src/stdlib.ts…/entries/semantic/17.actor-user-roles-to-positions.tsscripts/check-type-check-coverage.mjspackages/cli/src/commands/serve.tsThe runner-up is 13.7x shallower. And depth does not track size: the two largest linted files (236 KB, 183 KB) sit at depth 44 and 40.
What this means for the card's framing
The depth is not the generated content. It is
step17.rationale— a single+concatenation of 970 string fragments spanning lines 397–1367, while the nearest generated marker begins at line 1432. The file's own header saysrationaleis hand-written and outside the markers.So two of the three directions the card proposed would not have worked: splitting the generated output, and exempting the generated regions from parsing. The depth travels with
step17and is not between the markers to begin with. The permanent fix is to re-spell that one string (a template literal is depth ~4) — apackages/speccontent change, outside this card's declared file surface, filed as #10122.Reverse verification
Direction predicted in writing first, then observed. Decisive leg — identical
registry.tsbytes (git hash-object=5ff2ab96a0178a0ead8b72365b20d08469274edcboth legs), identical scope, only the flag differs:--stack-size=4000The red leg reproduces the CI message verbatim. Per the card's own demand, this is evidence that the stack ceiling moved — not a green CI run, which would prove nothing here since the gate already passed intermittently.
pnpm lintat the default stack was green on this machine, which is why the narrow-scope leg is the one that carries the proof. Reporting that rather than hiding it: a full-repo green is exactly the non-evidence the card warned about.Scope
Two files.
package.json— one script line; the@changesets/clirange and theversionscript (the #9465 epic fence) are untouched.eslint.config.mjs— comments only, no config change; it is the file that already documents how linting is wired, and it was named as possible surface on the claim..github/workflows/lint.ymldeliberately untouched.No changeset: a root script line and a comment block publish nothing.
Gates
Union derived from the real diff by
node scripts/pm/dispatch-gates.mjs(no paths — the script takes its own change set off the merge base), run after the final commit, at8b4677b:Run anyway, all at
8b4677b:pnpm lint— exit 0, no findings (the gate this PR changes);check:nul-bytes—OK (scanned 6369 text file(s) … no raw ASCII control bytes), self-test 75 assertions;check:slot-lookup—✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new;check:query-options-erasure—✓ query-options-erasure ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new.The last two import
eslint.config.mjs, so they are the gates coupled to the edited file; both counts are unchanged from theirorigin/mainreadings. Control-byte scan of both edited files: clean.package.jsonre-parsed as JSON;eslint.config.mjsre-imported (6 config blocks).Rebased state:
origin/mainwas re-fetched immediately before pushing and had not moved from1800ffac2(0 commits behind), so #10116 had not landed and there was no conflict to resolve.Related cards
eslint .intermittently dies with "Maximum call stack size exceeded" parsingmigrations/registry.ts— a monotonically-growing file now at the parser's stack cliff #10121 are duplicate reports of this same signature — [finding]eslint .intermittently dies with "Maximum call stack size exceeded" parsingmigrations/registry.ts— a monotonically-growing file now at the parser's stack cliff #10121 is still open and worth closing as a duplicate by triage.step17.rationaleis a 970-operand+chain (AST depth 976) — a template literal is depth ~4 #10122 — the permanent fix, filed from this work.Generated by Claude Code
Generated by Claude Code