Found in a read-only audit of v2.3.8. Verified against current main.
What happens
incremental_update refuses to reconcile a graph whose stored File nodes all live under a different root (code_review_graph/incremental.py:1372, the guard added for #889). The update tool does not always reach it.
code_review_graph/tools/build.py resolves an automatic diff base first, and when no usable anchor exists it sets full_rebuild = True and calls full_build() instead. full_build reconciles stale files with no root check, so a graph anchored to another repository is emptied and replaced rather than refused.
Reproduction
Build repository A into a shared data directory, then run an ordinary update for repository B with no usable stored SHA. The graph goes from holding only A/a.py to holding only B/b.py, with no mismatch error.
Why it matters
This is the same data-loss shape #889 fixed, reachable through the default path rather than an unusual spelling. The loss is regenerable graph data, never source files, which is why this is high and not critical.
Suggested fix
Apply the same guard before the fallback rebuild — a full build that is chosen automatically (rather than requested with --full) should refuse a graph anchored elsewhere, exactly as the incremental path does. An explicit full_rebuild=True from the user can keep its current wipe-and-rebuild behavior.
Found in a read-only audit of v2.3.8. Verified against current
main.What happens
incremental_updaterefuses to reconcile a graph whose storedFilenodes all live under a different root (code_review_graph/incremental.py:1372, the guard added for #889). Theupdatetool does not always reach it.code_review_graph/tools/build.pyresolves an automatic diff base first, and when no usable anchor exists it setsfull_rebuild = Trueand callsfull_build()instead.full_buildreconciles stale files with no root check, so a graph anchored to another repository is emptied and replaced rather than refused.Reproduction
Build repository A into a shared data directory, then run an ordinary
updatefor repository B with no usable stored SHA. The graph goes from holding onlyA/a.pyto holding onlyB/b.py, with no mismatch error.Why it matters
This is the same data-loss shape #889 fixed, reachable through the default path rather than an unusual spelling. The loss is regenerable graph data, never source files, which is why this is high and not critical.
Suggested fix
Apply the same guard before the fallback rebuild — a full build that is chosen automatically (rather than requested with
--full) should refuse a graph anchored elsewhere, exactly as the incremental path does. An explicitfull_rebuild=Truefrom the user can keep its current wipe-and-rebuild behavior.