fix(cache): retry zero-node semantic results (#2927)

A semantic result with no nodes and no hyperedges (only edges, or nothing) was cached and
stamped into the manifest, so an empty/degenerate LLM reply for a file froze that file:
detect_incremental saw it unchanged and never re-dispatched it. Reject zero-node results
from the cache read and write, drop edges from the manifest stamp tuple, and heal an
existing manifest by re-queueing files that were already stamped with a zero-node result.
This commit is contained in:
hopstreax authored and safishamsi committed 2026-08-21 18:26:49 +01:00
1 parent 5be36dac0d
commit 24cf52d51e
4 files changed
+460 -6

No files matched your search

+17
View File
@@ -992,6 +992,18 @@ def load_cached(path: Path, root: Path = Path("."), kind: str = "ast",
# across chunks without losing the truncated one (it stays partial).
if not allow_partial and isinstance(result, dict) and result.get("partial"):
return None
# A semantic entry with zero nodes and zero hyperedges is invalid (#2927):
# an edge-only or empty result (e.g. LLM omitted entities for the file)
# is not a valid standalone extraction. Treating it as a cache MISS
# ensures the file is re-dispatched and retried (#933/#1666).
if (
not allow_partial
and kind.startswith("semantic")
and isinstance(result, dict)
and not result.get("nodes")
and not result.get("hyperedges")
):
return None
if (
kind.startswith("semantic")
and isinstance(result, dict)
@@ -1543,6 +1555,11 @@ def save_semantic_cache(
)
if is_partial:
result = {**result, "partial": True}
# A semantic extraction with zero nodes and zero hyperedges is not a valid
# standalone extraction (#2927): edge-only or empty results must not be
# cached, so that subsequent runs can re-dispatch and retry the file (#933/#1666).
if not is_partial and not (result.get("nodes") or result.get("hyperedges")):
continue
save_cached(cache_path, result, root, kind=kind, cache_root=cache_root,
prompt=prompt, prompt_file=prompt_file)
saved += 1