Skip to content

The analyzer ingests its own output when -o sits inside -i #216

Description

@rahlk

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

  • A test runs the analyzer twice over one project with the output directory inside the input and asserts the second analysis.json is the same size as the first.
  • A test asserts an artifact outside the output directory is still captured, so the exclusion is not over-broad.
  • Relative and symlinked output paths resolve to the same exclusion.
  • Fixture disk usage is stable across three consecutive full-suite runs.

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions