You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Cached analysis.json not invalidated when tsc_only (resolver mode) changes — stale call graph returned #177
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).
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
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.
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.
Stays open pending #242 (epic #238): re-verify against the cants content-hash cache on the v2 backend rewire; close there with evidence if the new cache keys on resolver mode, or re-scope if not.
Root cause identified (2026-07-14): this is a special case of #269 — the TS backend's needs_run check (codeanalyzer.py:168) gates only on file existence, never on a content/config signal, so neither a source edit NOR a resolver-mode (tsc_only) change invalidates the cached analysis.json. Should close with #269's fix or fold in.
Both issues are the same root cause — needs_run at cldk/analysis/typescript/codeanalyzer/codeanalyzer.py:168 uses file existence as its only freshness signal — and #269 already identified this one as a special case of it. Everything here is preserved in #269: the resolver-mode reproduction in both directions, the analysis_level gap you flagged as related, and the three proposed fixes (manifest sidecar / mode-namespaced cache file / args hashed into the filename).
#269 now covers all three invalidation triggers (source content, tsc_only, analysis_level) with a regression test required for each. Follow it there.
Activity
rahlk commentedon Jul 14, 2026
Stays open pending #242 (epic #238): re-verify against the cants content-hash cache on the v2 backend rewire; close there with evidence if the new cache keys on resolver mode, or re-scope if not.
rahlk commentedon Jul 14, 2026
Root cause identified (2026-07-14): this is a special case of #269 — the TS backend's needs_run check (codeanalyzer.py:168) gates only on file existence, never on a content/config signal, so neither a source edit NOR a resolver-mode (tsc_only) change invalidates the cached analysis.json. Should close with #269's fix or fold in.
rahlk commentedon Aug 3, 2026
Closing as merged into #269.
Both issues are the same root cause —
needs_runatcldk/analysis/typescript/codeanalyzer/codeanalyzer.py:168uses file existence as its only freshness signal — and #269 already identified this one as a special case of it. Everything here is preserved in #269: the resolver-mode reproduction in both directions, theanalysis_levelgap you flagged as related, and the three proposed fixes (manifest sidecar / mode-namespaced cache file / args hashed into the filename).#269 now covers all three invalidation triggers (source content,
tsc_only,analysis_level) with a regression test required for each. Follow it there.