Describe the bug
inventoryArtifacts (src/artifacts/index.ts:36) walks the project with walk(root) and name-only SKIP_DIRS (node_modules, .git, .codeanalyzer, dist, build, out, coverage, .next, .turbo, .cache, vendor), then captures each file's text whole — the code's own comment records the deliberate no-byte-cap decision. Nothing tells the walk where the run writes, so when --output points inside --input, run N ingests run N−1's analysis.json as an artifact and embeds it verbatim. Each run squares the last.
The visible symptom is not a wrong answer but an OOM kill while decoding, which reads as a flaky or load-sensitive test suite and is easy to misdiagnose as one. codeanalyzer-python carried the identical bug and had accumulated 207 GB across five fixtures, including a 97 GB analysis.json grown from a 4 KB fixture, before it was found.
An output directory coincidentally named out, dist, or build is covered today by SKIP_DIRS; any other name is not.
To Reproduce
Run the analyzer three times with --output inside --input (e.g. -o <project>/.output) and watch analysis.json grow by roughly its own size squared each run.
Expected behavior
Ported from codellm-devkit/codeanalyzer-python#207 (fix: codellm-devkit/codeanalyzer-python#216).
Additional context
Scope boundary
inventoryArtifacts already receives opts, which carries output — it just never consults it. In scope: skipping the resolved output and cache paths during the walk. Out of scope: changing what is otherwise captured, the whole-or-nothing text contract, or SKIP_DIRS for unrelated names.
Goals
- A run whose output directory sits inside the input never ingests its own output.
- Repeated runs with the same flags produce a stable-size
analysis.json.
- The exclusion derives from resolved paths, not from hardcoded directory names — relative,
..-laden, and symlinked targets all resolve to the same exclusion, and a target outside the project excludes nothing.
Caveats and known risks
- An output directory that is the project root cannot be skipped wholesale without emptying the inventory. codeanalyzer-python handled this by also excluding the individual output files (
analysis.json, graph.cypher), which costs one file instead of the whole tree.
- A genuinely pre-existing file living in the chosen output directory becomes invisible. That is the right trade, but it is a trade.
- This changes the artifact inventory for anyone relying on output-inside-input being captured — payload content, not schema shape.
Describe the bug
inventoryArtifacts(src/artifacts/index.ts:36) walks the project withwalk(root)and name-onlySKIP_DIRS(node_modules, .git, .codeanalyzer, dist, build, out, coverage, .next, .turbo, .cache, vendor), then captures each file's text whole — the code's own comment records the deliberate no-byte-cap decision. Nothing tells the walk where the run writes, so when--outputpoints inside--input, run N ingests run N−1'sanalysis.jsonas an artifact and embeds it verbatim. Each run squares the last.The visible symptom is not a wrong answer but an OOM kill while decoding, which reads as a flaky or load-sensitive test suite and is easy to misdiagnose as one. codeanalyzer-python carried the identical bug and had accumulated 207 GB across five fixtures, including a 97 GB
analysis.jsongrown from a 4 KB fixture, before it was found.An output directory coincidentally named
out,dist, orbuildis covered today bySKIP_DIRS; any other name is not.To Reproduce
Run the analyzer three times with
--outputinside--input(e.g.-o <project>/.output) and watchanalysis.jsongrow by roughly its own size squared each run.Expected behavior
analysis.jsonis the same size as the first.Ported from codellm-devkit/codeanalyzer-python#207 (fix: codellm-devkit/codeanalyzer-python#216).
Additional context
Scope boundary
inventoryArtifactsalready receivesopts, which carriesoutput— it just never consults it. In scope: skipping the resolved output and cache paths during the walk. Out of scope: changing what is otherwise captured, the whole-or-nothing text contract, orSKIP_DIRSfor unrelated names.Goals
analysis.json...-laden, and symlinked targets all resolve to the same exclusion, and a target outside the project excludes nothing.Caveats and known risks
analysis.json,graph.cypher), which costs one file instead of the whole tree.