Repository navigation
Description
code-review-graph version
2.3.9
Operating system
Linux
Python version
3.13.13
AI platform
claude-code
Output of code-review-graph status
Nodes: 6
Edges: 6
Files: 2
Languages: python
Last updated: 2026-10-08T17:23:31
Built on branch: master
Built at commit: f728796deb53
Steps to reproduce
Reproduced on code-review-graph 2.3.9, Python 3.13.13, Linux, using an isolated Git repository. No MCP server, filesystem watcher, or manual database modification is required.
1. Create a minimal Git repository
Create /tmp/crg-noop-repro and src/:
R=/tmp/crg-noop-repro
mkdir -p "$R/src"
cd "$R"Create src/mod.py:
def alphaHelper():
return 1
def betaCaller():
return alphaHelper()Create src/util.py:
def deltaHelper(value):
return value * 2Initialize and commit the repository:
R=/tmp/crg-noop-repro
cd "$R"
git init -q
git add src/mod.py src/util.py
git -c user.name=Test -c user.email=test@example.com commit -qm init2. Build a healthy graph
code-review-graph build --repo "$R" --skip-flowsInitial state: 5 nodes, 0 missing FTS entries, 0 stale FTS row IDs.
3. Modify the tracked file
Append to src/mod.py:
def gammaHelper():
return alphaHelper() + 24. Update without post-processing
code-review-graph update --repo "$R" --skip-postprocessObserved output:
Incremental: 1 files updated, 4 nodes, 5 edges (postprocess=none)
The command succeeds. Skipping post-processing is intentional, but the graph now contains stale FTS data while build_state is recorded as complete.
5. Run a normal update without further source changes
code-review-graph update --repo "$R" --skip-flowsObserved output:
Incremental: 0 files updated, 0 nodes, 0 edges (postprocess=minimal)
The command succeeds but does not repair the FTS index.
6. Verify FTS consistency
Compare nodes.id with nodes_fts_docsize.id using read-only SQLite queries, and check FTS search results joined to existing graph nodes.
Recorded results:
| Stage | Nodes | Missing FTS | Stale FTS IDs | Build state | gammaHelper / alphaHelper |
|---|---|---|---|---|---|
| Initial build | 5 | 0 | 0 | complete | — / found |
After --skip-postprocess |
6 | 4 | 3 | complete | not found / not found |
| After no-op update | 6 | 4 | 3 | complete | not found / not found |
| After explicit postprocess | 6 | 0 | 0 | complete | found / found |
7. Repair explicitly
code-review-graph postprocess --repo "$R" --no-flows --no-communitiesObserved output:
Post-processing: 6 FTS entries
After post-processing, the missing and stale FTS counts return to zero, and both gammaHelper and alphaHelper become searchable.
Expected vs actual behavior
Expected:
--skip-postprocessmay intentionally defer FTS synchronization, but the resulting derived-state incompleteness should not be reported as fully processed.- A subsequent normal
updateshould either repair detectable FTS drift or explicitly report that derived state remains incomplete. - Successful source-graph updates should not implicitly certify the consistency of derived search indexes.
Actual:
update --skip-postprocessleaves FTS inconsistent but recordsbuild_state=complete.- A subsequent no-op
updatereturns successfully without performing FTS synchronization and again recordscomplete. code-review-graph statusreports no incomplete-build warning.- Both newly added and unchanged symbols remain unfindable through valid FTS lookups.
- Only explicit
postprocessrestores index consistency.
Additional context
Root cause
Two related paths in code_review_graph/tools/build.py contribute to the issue:
- No-op early return (approximately lines 702–719): When
files_updated == 0and there are no parse errors, the function setsBUILD_COMPLETEand returnsstatus: okbefore_run_postprocess()executes. The existingupdate_fts_index()recovery logic is therefore bypassed. - Skipped post-processing (approximately lines 771–772): The
postprocess="none"path can setBUILD_COMPLETEeven though FTS synchronization was intentionally skipped.
This conflicts with the definition in build_state.py:
Post-processing finished over a whole graph.
The issue is not that --skip-postprocess allows skipping post-processing. That behavior is intentional. The problem is that the incomplete derived state is represented as complete, and a subsequent normal update cannot detect or recover it.
Related upstream work
- [Bug]: MCP
serve --auto-watchskips FTS post-processing after file updates #1103: MCPserve --auto-watchskips post-processing. This issue is independent: it reproduces using only CLI operations, without MCP or a watcher. Fixing [Bug]: MCPserve --auto-watchskips FTS post-processing after file updates #1103 would not address this recovery path. - perf(search): rewrite only changed files' FTS entries on an update #1008: Introduced incremental FTS synchronization, including handling added and removed entries. The no-op early return bypasses that mechanism.
- Stop the graph and the daemon from corrupting quietly #1028: Added interrupted-build recovery using incomplete build-state markers. It does not cover stale derived state incorrectly marked
complete.
Suggested direction
Consider a lightweight FTS consistency check and recovery path for normal no-op updates, together with accurate derived-state reporting when post-processing has intentionally been skipped.
Avoid requiring an unnecessary full graph rebuild for every healthy no-op update.
Regression coverage should verify that detectable FTS drift is repaired or explicitly reported when no source files have changed, while healthy no-op updates remain inexpensive.
Scope: This report concerns detectable FTS drift. Out-of-band FTS index corruption that is invisible to the existing nodes_fts_state mirror is outside its scope.
Activity