Skip to content

stat-index.json keyed by absolute path and never pruned: 0% cache hits for any root that didn't write it (11,614 entries for 1,130 files across 4 roots) #2199

Description

@aeyeopsdev

Summary

graphify-out/cache/stat-index.json is keyed by absolute path and never prunes entries for roots that are no longer in play. When the same corpus is reached through more than one absolute path — a second mount of the same share, a CI/snapshot clone under /tmp, a moved checkout — the index accumulates one full set of entries per root, and a run from a root that isn't the one that wrote the index gets zero cache hits: every file re-extracts, every run, forever.

Observed

A ~1,130-file repo, graphify 0.9.17, on macOS. Every graphify update . reported all files uncached:

AST extraction: 1131/1131 uncached files (100%) [16 workers]

The stat index had grown to 11,614 entries for ~1,130 files — four roots, three of them dead:

root entries
/System/Volumes/Data/mnt/dev/mm/<repo> 3198
/mnt/j/dev/mm/<repo> (same share, second mount path) 3062
/tmp/platform_snap.QSy9DT/repo (throwaway clone, gone) 2677
/tmp/platform_snap2.kaYXPI/repo (throwaway clone, gone) 2677

The index that happened to be committed was written from the /mnt/j root, so on the machine running from /System/Volumes/Data/... nothing matched.

Deleting stat-index.json restored caching immediately — the next run reported 3181 files cached/unchanged, 0 re-extracted. It regrows the same way.

Why this is worth fixing rather than documenting

  • The failure is silent: output says "uncached", which reads as "these files changed", not "your cache is keyed to a path you aren't at". Nothing points at the index.
  • The cost scales with corpus size — here it was a full re-extract of 1,130 files on every run, plus a full rewrite of a 40 MB graph.json.
  • graphify-out/ is documented as commit-worthy, so a cache index written on one machine travels to machines with a different absolute root, where it is guaranteed to miss.
  • Dead roots are never reclaimed. Two of the four here were /tmp clones that had not existed for days.

manifest.json already stores relative keys, and the incremental reference explicitly calls this out for the manifest (#1417: "manifest keys stay relative to the scan root — portable across clones/machines, so --update keeps matching cached files instead of missing every one after a move"). The stat index appears to have missed that same treatment.

Suggested fix

  1. Key stat-index.json relative to the scan root, as manifest.json already is.
  2. Failing that, drop entries whose root is not the current scan root when writing the index, so it stays bounded and self-heals instead of accumulating.
  3. Either way, warn when a run gets ~0% cache hits against a non-empty index — that would have made this self-diagnosing.

Related: #1417 (relative manifest keys), #2197 (absolute source_file poisoning the semantic cache) — same root cause in a different artifact.

Environment

graphify 0.9.17, macOS 25.5 (Darwin), Python 3.11 install via uv. Corpus reached through two mount paths of one network share plus transient /tmp snapshot clones.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions