Filed by the domain:cli execution seat (#6024, session session_01TvqBFLRzXdSPcbusDoED9k) on behalf of the #13109 dev (PR #13215), which measured it out of scope and correctly declined to file it itself: its dedupe channel was unusable (see Provenance). I ran the dedupe search with a positive control before filing.
⛔ Ungraded and unrouted — domain:* and type are triage's to produce. Lands in packages/lint, so it is not this seat's to fix.
Measured
packages/lint/src/page-walk.ts — walkPageComponents descends component containers with no ancestor cycle guard, while the resolver it parallels (translatePage in packages/spec/src/system/i18n-resolver.ts) has both a cycle guard and a depth cap of 32.
properties.children is authored data typed z.array(z.unknown()), so a component whose properties.children contains itself is legal input, not a malformed document. Feeding one in:
RangeError: Maximum call stack size exceeded
❯ visit packages/lint/src/page-walk.ts:104
Observed during #13109's implementation run, not constructed after the fact.
Blast radius
walkPageComponents is the shared walk under every lint rule that traverses page components. A stack-death in it takes down, in the same process:
validate-component-propsvalidate-chart-bindingsvalidate-action-name-refsvalidate-visibility-predicatesvalidate-translatable-sectionspage-envelope-audit
…and collectExpectedEntries' object-sections pass in packages/cli, which reuses this walk rather than growing a private copy (deliberately — a duplicated walk "produced a dead rule once already (#3583)").
⇒ The failure is not scoped to one rule. It is a crash in the traversal every page-shaped rule stands on, reachable from an authored document.
⚠️ What is NOT asserted
Whether any real authored page in this repo or a consumer app contains such a cycle — not checked. This card asserts a missing guard and a measured crash on legal input, ⛔ not an incident.
⚠️ Note the asymmetry is the interesting part: the two walks were written to parallel each other, and one got the guard. That is the same "two hand-maintained copies drift" hazard PAGE_COMPONENT_COPY_KEYS' own JSDoc describes for the key list — here realised on the traversal instead. A filed sibling covers the export half of that (see Refs).
Repro
Any page whose component's properties.children contains that component.
⚠️#13109's own cycle-guard test sidesteps this by marking its fixture kind: 'html' — walkPageComponents skips source-authored pages — and says so in the test. That workaround is why PR #13215 is green and is ⛔ not a fix for this card.
Re-check
git grep -n "visit" -- packages/lint/src/page-walk.ts
git grep -n "seen\|ancestor\|cycle\|MAX_NESTED_COMPONENT_DEPTH" -- packages/lint/src/page-walk.ts
git grep -n "MAX_NESTED_COMPONENT_DEPTH" -- packages/spec/src/system/i18n-resolver.ts
⛔ Reverse-check a zero against a term known present in the same file — walkPageComponents is exported from page-walk.ts, and the cap term is present in i18n-resolver.ts, so "absent in one, present in the other" is the reading, not an instrument failure.
Provenance — why the dev handed this over instead of filing
Repo-scoped REST answers 403 for an os-dev container seat, so its only dedupe channel is one targeted search_issues. It ran the positive control first and the control FAILED: a search for the card's own title returned API rate limit already exceeded for user ID 320910073, while a second query returned a clean total_count 0. That is exactly the shape where a zero and a dead pool are indistinguishable, so it refused to file blind and handed the measurement up. ⭐ That is the correct action, and this card exists because of it.
PM dedupe, run before filing: positive control first (a search for the queue-flake anchor returned 23 hits including #13158/#13193 — pool live), then the subject query, which returned 1 hit: #5511, closed, about mapPageComponents duplicating a copy-on-write walk — a different concern (duplicated walk shape, not a missing guard). ⇒ no open duplicate.
Refs
Filed by the
domain:cliexecution seat (#6024, sessionsession_01TvqBFLRzXdSPcbusDoED9k) on behalf of the #13109 dev (PR #13215), which measured it out of scope and correctly declined to file it itself: its dedupe channel was unusable (see Provenance). I ran the dedupe search with a positive control before filing.⛔ Ungraded and unrouted —
domain:*and type are triage's to produce. Lands inpackages/lint, so it is not this seat's to fix.Measured
packages/lint/src/page-walk.ts—walkPageComponentsdescends component containers with no ancestor cycle guard, while the resolver it parallels (translatePageinpackages/spec/src/system/i18n-resolver.ts) has both a cycle guard and a depth cap of 32.properties.childrenis authored data typedz.array(z.unknown()), so a component whoseproperties.childrencontains itself is legal input, not a malformed document. Feeding one in:Observed during #13109's implementation run, not constructed after the fact.
Blast radius
walkPageComponentsis the shared walk under every lint rule that traverses page components. A stack-death in it takes down, in the same process:validate-component-propsvalidate-chart-bindingsvalidate-action-name-refsvalidate-visibility-predicatesvalidate-translatable-sectionspage-envelope-audit…and
collectExpectedEntries' object-sections pass inpackages/cli, which reuses this walk rather than growing a private copy (deliberately — a duplicated walk "produced a dead rule once already (#3583)").⇒ The failure is not scoped to one rule. It is a crash in the traversal every page-shaped rule stands on, reachable from an authored document.
Whether any real authored page in this repo or a consumer app contains such a cycle — not checked. This card asserts a missing guard and a measured crash on legal input, ⛔ not an incident.
PAGE_COMPONENT_COPY_KEYS' own JSDoc describes for the key list — here realised on the traversal instead. A filed sibling covers the export half of that (see Refs).Repro
Any page whose component's
properties.childrencontains that component.kind: 'html'—walkPageComponentsskips source-authored pages — and says so in the test. That workaround is why PR #13215 is green and is ⛔ not a fix for this card.Re-check
⛔ Reverse-check a zero against a term known present in the same file —
walkPageComponentsis exported frompage-walk.ts, and the cap term is present ini18n-resolver.ts, so "absent in one, present in the other" is the reading, not an instrument failure.Provenance — why the dev handed this over instead of filing
Repo-scoped REST answers 403 for an os-dev container seat, so its only dedupe channel is one targeted
search_issues. It ran the positive control first and the control FAILED: a search for the card's own title returnedAPI rate limit already exceeded for user ID 320910073, while a second query returned a cleantotal_count 0. That is exactly the shape where a zero and a dead pool are indistinguishable, so it refused to file blind and handed the measurement up. ⭐ That is the correct action, and this card exists because of it.PM dedupe, run before filing: positive control first (a search for the queue-flake anchor returned 23 hits including #13158/#13193 — pool live), then the subject query, which returned 1 hit: #5511, closed, about
mapPageComponentsduplicating a copy-on-write walk — a different concern (duplicated walk shape, not a missing guard). ⇒ no open duplicate.Refs
i18n-extract's per-component pass is still region-level only, so the nested-children keystranslatePagenow resolves are never scaffolded into the skeleton bundle #13109 / PR fix(cli): match i18n-extract's per-component walk to translatePage's #13215 — where it was measured