Skip to content

[Bug]: No-op update leaves stale FTS marked complete after --skip-postprocess #1104

Closed

Description

@Ariosolzq

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 * 2

Initialize 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 init

2. Build a healthy graph

code-review-graph build --repo "$R" --skip-flows

Initial 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() + 2

4. Update without post-processing

code-review-graph update --repo "$R" --skip-postprocess

Observed 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-flows

Observed 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-communities

Observed 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-postprocess may intentionally defer FTS synchronization, but the resulting derived-state incompleteness should not be reported as fully processed.
  • A subsequent normal update should 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-postprocess leaves FTS inconsistent but records build_state=complete.
  • A subsequent no-op update returns successfully without performing FTS synchronization and again records complete.
  • code-review-graph status reports no incomplete-build warning.
  • Both newly added and unchanged symbols remain unfindable through valid FTS lookups.
  • Only explicit postprocess restores index consistency.

Additional context

Root cause

Two related paths in code_review_graph/tools/build.py contribute to the issue:

  1. No-op early return (approximately lines 702–719): When files_updated == 0 and there are no parse errors, the function sets BUILD_COMPLETE and returns status: ok before _run_postprocess() executes. The existing update_fts_index() recovery logic is therefore bypassed.
  2. Skipped post-processing (approximately lines 771–772): The postprocess="none" path can set BUILD_COMPLETE even 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

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions