Conversation
`out.push(...walkFilesystem(full, includes))` turns the child's array into push()'s argument list, and V8 caps that at roughly 128k. A single unignored subtree with more paths than that throws "Maximum call stack size exceeded" at trivial directory depth — the message is about the argument list, not recursion. Only the git-less path reaches this: when listGitFiles returns null the walker collects every non-dot file first and filters by language later, so any large cache, browser profile or dataset directory in a repo that was never `git init`-ed is fatal. Appending in a loop removes the cap without changing what is collected or in what order. Two tests, deliberately split: - a cheap one that pins the accumulation itself (a wide, deep subtree outside Git comes back flat and complete). It runs everywhere and would catch a botched loop, but it cannot reach the argument cap. - one that actually reproduces the crash with 150k files. It fails with "Maximum call stack size exceeded" on the previous code and passes with this one. CI also runs windows-latest, where creating that many files is far slower, so it is skipped there. Measured while writing this: on Node 22 a spread of 125k elements still succeeds and 150k throws, and `--stack-size` does not move the boundary — it is an argument-count limit, not stack depth.
🌱 graft blast radius1 area changed → 2 areas can be affected. 4 dependent symbols, depth 2. flowchart TB
A0(("Dependency Graph<br/>3 symbols"))
A1(("Context Discovery<br/>1 symbol"))
classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
class A0,A1 reached;
Who knows this code — 4 people across 3 areas
Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no All 4 dependent symbols, grouped by areaDependency Graph — 3 symbols in 3 files
Context Discovery — 1 symbol in 1 file
Test signal per changed area — 1 ✗Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.
Open the interactive graph → — click an area to see its dependent symbols at file:line. |
What
walkFilesystemappends a child subtree withwhich makes the child array the argument list of
push(). V8 caps that at roughly 128k, so a single unignored subtree with more paths than that throwsRangeError: Maximum call stack size exceededat trivial directory depth — the message is about the argument list, not recursion depth.Appending in a loop removes the cap. Nothing else changes: same paths, same order.
Fixes #314
Why it only bites some repos
Only the git-less path reaches this. When
listGitFilesreturnsnullthe walker collects every non-dot file first and filters by language later, so any large cache, browser profile or dataset directory in a repo that was nevergit init-ed is fatal — while the same tree under Git is fine becausegit ls-filesrespects.gitignore. That is what makes it look nondeterministic across a fleet, as the reporter described.Tests
Two, deliberately split, because one of them cannot do the other's job:
walkDir collects a large nested subtree outside Git without dropping paths— a wide, deep subtree outside Git comes back flat and complete. Runs on every platform, ~40ms. It pins the accumulation and would catch a botched loop, but it cannot reach the argument cap, so it passes on the old code too. I am flagging that rather than presenting it as proof.walkDir survives a subtree larger than V8's argument limit (#314)— the real reproduction, 150k files. On the previous code it fails with exactlyMaximum call stack size exceeded; with this change it passes.Verified both directions: reverting only
src/ingest/fs.tsand re-running gives# pass 18 / # fail 1, and the one failure is test 2 with that message. With the fix,# pass 19 / # fail 0in 6.9s.The heavy test is skipped on Windows. CI runs
windows-latesttoo, and creating 150k files there is far slower than the ~2.5s it takes on Linux. If you would rather not pay even the Linux cost in CI, say so and I will drop test 2 and keep test 1 — the fix stands on its own either way.Measurements taken while writing this
--stack-sizedoes not move that boundary. I tried 500/200/100 with 2k–10k elements: all fine. It is an argument-count limit, not stack depth — worth knowing if anyone later tries to make the test cheaper by shrinking the stack.push(...)sites insrc/graph/build.ts(nodes,rawEdges) accumulate per file rather than per subtree, so they do not reach the cap. Left untouched to keep this focused.