Repository navigation
Description
Problem
discover_artifacts walks project_dir.rglob("*") and captures every non-.py file's whole text (codeanalyzer/artifacts/discovery.py:102-118). It receives only project_dir — nothing tells it where the run's output goes. So when -o points inside -i, run N ingests run N−1's analysis.json as an artifact and embeds it verbatim in its own output.
Each run therefore squares the previous one. This is not theoretical: test_cli.py does exactly this (fixture_dir.joinpath(".output") at test/test_cli.py:23,74,114), and the checked-out tree had accumulated 207 GB across five fixtures, including a 97 GB analysis.json grown from a 4 KB single-file fixture.
The visible symptom is not a wrong answer, it is a SIGKILL: the analyzer allocates 40–50 GB decoding the file and the OS kills it. That reads as a flaky/load-sensitive test suite, so it has been misdiagnosed as such more than once.
Reproduce
python3 -m codeanalyzer -i test/fixtures/single_functionalities/decorators_and_hof \
-a 2 --no-venv -o test/fixtures/single_functionalities/decorators_and_hof/.output
Run it three times. .output/analysis.json grows by roughly its own size squared each time. By run five the process dies with Signals.SIGKILL: 9 and subprocess.CalledProcessError if it was spawned by a test.
Scope boundary
discover_artifacts learns which directory the run writes to and skips it, the same way _IGNORED_DIRS already skips .codeanalyzer, virtualenv and site-packages. In scope: threading the resolved output directory from core.analyze into discover_artifacts, and the same for the cache directory if it can also sit inside the project. Out of scope: changing what artifacts are captured otherwise, the capture_text contract, or _IGNORED_DIRS for unrelated names.
test_cli.py writing -o inside the fixture is legitimate usage and should keep working — the analyzer is what needs the guard, not the test. Moving the test's output elsewhere would hide the bug rather than fix it.
Goals
- An analyzer run whose
-o(and-c) sit inside-inever ingests its own output. - Repeated runs over the same project with the same flags produce a stable-size
analysis.json. - The exclusion is derived from the actual resolved paths, not from a hardcoded
.outputname — any-otarget works.
Caveats and known risks
- The output directory may not exist yet when discovery runs, and may be given as a relative path or through a symlink; the comparison has to be on resolved paths, not string prefixes.
-ooutside the project (the normal case) must be unaffected — no artifact silently dropped because a resolve went wrong.- Excluding the output dir changes the artifact inventory for anyone who was relying on
-oinside-ibeing captured. That is the bug, but it is an L1 payload change and the monotonicity gate compares payloads, so the fixtures' expected artifact counts may need regenerating. - Genuinely pre-existing files that happen to live in the chosen output directory become invisible. Skipping the whole directory is the right trade, but it is a trade.
Definition of done
- A test runs the analyzer twice over one project with
-oinside-iand asserts the secondanalysis.jsonis the same size as the first (this is what actually fails today). - A test asserts an artifact outside the output directory is still captured, so the exclusion is not over-broad.
- Relative and symlinked
-opaths resolve to the same exclusion. -
test_cli.pykeeps writing into the fixture directory and the suite stays green with no fixture growth across consecutive runs. -
du -sh test/fixturesis stable across three consecutive full-suite runs.
Activity
fix(artifacts): the run's own output is not an input (#207) (#216)