PROBLEM
The TypeScript backend reuses a cached analysis.json whenever one exists and eager=False — with NO check that the source is unchanged. cldk/analysis/typescript/codeanalyzer/codeanalyzer.py:168:
needs_run = self.eager_analysis or not analysis_json_file.exists() or bool(self.target_files)
So after any edit to the analyzed project, a re-analysis with the default eager=False silently returns the PRIOR analysis.json — stale symbol table, call graph, and (schema v2) L3/L4 body/edge data. The analyzer subprocess never runs, so cants' own content-hash cache never gets a chance to detect the change. The only escape is passing eager=True on every call, which defeats caching entirely.
Contrast: the Python backend (codeanalyzer.py:239) always invokes analyzer.analyze() and delegates freshness to codeanalyzer-python's content-hash cache — so Python invalidates correctly on source change. TS is the odd one out.
WHY IT MATTERS NOW (schema v2 / rc.1)
Under schema v2, sub-callable vertices (statements, CFG/DDG nodes) are addressed by ORDINAL ids (@line:col) that are deliberately NOT durable across edits. Any L3/L4 consumption API that seeds queries by source location (slice_backward("src/util.ts:42"), flows-to, def-use) relies on cache invalidation to keep line:col honest. With this bug, a stale cache returns vertices pointing at the OLD line numbers — a silent wrong-answer, the worst failure mode for slice/flow evidence. This blocks the "staleness is handled by cache invalidation" assumption the L3/L4 API design rests on.
RELATED
#177 (cache not invalidated when tsc_only/resolver mode changes) is a SPECIAL CASE of this same root cause (no freshness check at :168) — it should close with this fix or be folded in.
FIX DIRECTION
- Gate needs_run on a content signal, not mere file existence: compare a hash/mtime of the project's TS sources (or the top-level content_hash cants already emits per module) against what produced the cached analysis.json; re-run when they differ. Or: always invoke the analyzer and let cants' content-hash cache decide (parity with the Python path) — measure the cold-vs-warm cost first, since that removes the SDK-level short-circuit.
- Either way: editing a source file and re-analyzing with eager=False must yield fresh results.
DEFINITION OF DONE
Blocks the schema-v2 L3/L4 consumption API design (epic #238, F6 #244); precondition for location-seeded slice/flow queries.
PROBLEM
The TypeScript backend reuses a cached analysis.json whenever one exists and eager=False — with NO check that the source is unchanged.
cldk/analysis/typescript/codeanalyzer/codeanalyzer.py:168:So after any edit to the analyzed project, a re-analysis with the default eager=False silently returns the PRIOR analysis.json — stale symbol table, call graph, and (schema v2) L3/L4 body/edge data. The analyzer subprocess never runs, so cants' own content-hash cache never gets a chance to detect the change. The only escape is passing eager=True on every call, which defeats caching entirely.
Contrast: the Python backend (codeanalyzer.py:239) always invokes
analyzer.analyze()and delegates freshness to codeanalyzer-python's content-hash cache — so Python invalidates correctly on source change. TS is the odd one out.WHY IT MATTERS NOW (schema v2 / rc.1)
Under schema v2, sub-callable vertices (statements, CFG/DDG nodes) are addressed by ORDINAL ids (@line:col) that are deliberately NOT durable across edits. Any L3/L4 consumption API that seeds queries by source location (slice_backward("src/util.ts:42"), flows-to, def-use) relies on cache invalidation to keep line:col honest. With this bug, a stale cache returns vertices pointing at the OLD line numbers — a silent wrong-answer, the worst failure mode for slice/flow evidence. This blocks the "staleness is handled by cache invalidation" assumption the L3/L4 API design rests on.
RELATED
#177 (cache not invalidated when tsc_only/resolver mode changes) is a SPECIAL CASE of this same root cause (no freshness check at :168) — it should close with this fix or be folded in.
FIX DIRECTION
DEFINITION OF DONE
Blocks the schema-v2 L3/L4 consumption API design (epic #238, F6 #244); precondition for location-seeded slice/flow queries.