Skip to content

Cached analysis.json not invalidated when tsc_only (resolver mode) changes — stale call graph returned #177

Description

@rahlk

Summary

The cached analysis.json is not invalidated when tsc_only changes. The flag materially changes analyzer output (the tsc resolver produces a different call graph and an empty synthesized_callables, vs. the Jelly default), but it is not part of the needs_run cache-bust predicate. Toggling tsc_only against the same project silently returns the stale graph recorded under the previous mode.

Found while reviewing the tsc_only work on fix/issue-174 (#174).

Where

cldk/analysis/typescript/codeanalyzer/codeanalyzer.py:168:

needs_run = self.eager_analysis or not analysis_json_file.exists() or bool(self.target_files)

bool(self.target_files) is in the predicate precisely because it changes what the analyzer produces; self.tsc_only has the same property but is not included. The cache file is a fixed analysis.json with no encoding of the resolver mode it was produced under (no sidecar manifest, no fingerprint). This is not an opt-in path — typescript_analysis.py resolves a default cache dir (<project>/.codeanalyzer/typescript) even when cache_dir=None, so every TypeScript run hits it.

Reproduce

  1. CLDK.typescript(project_path=P, backend=TSCodeAnalyzerConfig(tsc_only=False)) → first run, no cache, binary runs with the Jelly resolver, writes analysis.json with anonymous-callback edges and a populated synthesized_callables.
  2. CLDK.typescript(project_path=P, backend=TSCodeAnalyzerConfig(tsc_only=True)) against the same project → needs_run evaluates to False, the binary is never invoked, and the stale Jelly-produced analysis.json is re-hydrated. The caller asked to suppress Jelly resolution but gets Jelly edges and a non-empty synthesized_callables anyway.

The reverse direction is symmetric: a tsc-only cache served to a caller who asked for the default Jelly resolution. No error, no warning.

Fix

Any of:

  • Include the resolver mode in the predicate via a recorded prior value (e.g. an analysis.json.manifest sidecar of the output-affecting flags; force a re-run on mismatch).
  • Namespace the cache file by mode (analysis.json for Jelly, analysis.tsc_only.json for tsc-only) so the two modes cache independently.
  • Hash the output-affecting args into the cache file name.

Related

The pre-existing analysis_level parameter has the same gap — it's in the CLI args but not in needs_run. Worth addressing together; the existing inconsistency is what makes adding another mode knob a footgun.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions