Skip to content

fix: repair stale FTS on no-op updates - #1105

Merged
4 commits merged into
tirth8205:stagingfrom
Ha1baraA11:pr/code-review-graph-1104
Nov 6, 2026
Merged

4 commits merged into
tirth8205:stagingfrom
Ha1baraA11:pr/code-review-graph-1104

Conversation

@Ha1baraA11

Copy link
Copy Markdown
Contributor

Linked issue

Closes #1104

What & why

  • Leave build_state as postprocess-pending when an update skips post-processing.
  • Let a later update repair pending derived data without forcing a full graph rebuild.
  • Reconcile detectable FTS drift on no-op updates, including graphs from older versions
    that incorrectly kept the complete marker.
  • Added regression tests for pending-state repair and legacy complete-state FTS drift.

How it was tested

  • uv run ruff check code_review_graph/ — passed.
  • uv run pytest tests/ --tb=short -q — 4,270 passed, 850 skipped, 2 xfailed, 2 xpassed.
    Used watchdog's KqueueObserver because native FSEvents segfaulted on this macOS host.

Checklist

  • Tests added for new functionality
  • All tests pass: uv run pytest tests/ --tb=short -q
  • Linting passes: uv run ruff check code_review_graph/
  • Type checking passes: uv run mypy code_review_graph/ --ignore-missing-imports --no-strict-optional
  • Lines are at most 100 characters
  • Docs updated where behavior changed (README, docs/, docstrings)

@Ariosolzq

Copy link
Copy Markdown
Contributor

Thanks for working on #1104! The fix looks promising, but I noticed two potential regressions:

1. postprocess-pending may be reported as healthy.

Changing build_was_interrupted() to check only BUILD_IN_PROGRESS also affects CLI status, which uses the same function. A graph with pending post-processing may no longer report INCOMPLETE.

The existing opt-in test test_kill_after_the_anchor_is_visible_as_an_incomplete_build covers this behavior.

2. Partial parse failures may restore an incorrect complete state.

When postprocess="none" and some files fail parsing, the new branch restores previous_state. If other files were successfully updated, FTS may still be stale even though the state returns to complete.

Could we preserve the distinction between needing a full rebuild and having incomplete derived state, and add regression coverage for partial parse failures?

Thanks again for the contribution!

@Ha1baraA11

Copy link
Copy Markdown
Contributor Author

Thanks for catching these. I checked the current code and confirm both are still open:

  • status uses build_was_interrupted(), which now returns true only for BUILD_IN_PROGRESS; a postprocess-pending graph can therefore lose the INCOMPLETE status. The existing after-anchor interruption test still expects that status.
  • When an incremental update stores some files but other files fail parsing with postprocess="none", the final branch restores previous_state. If the prior state was complete, this can hide stale FTS data.

This PR is not ready to treat either case as fixed. The correction should keep the user-visible pending state distinct from “needs a full source rebuild,” and keep derived-data state pending after partial parse failures.

@tirth8205

Copy link
Copy Markdown
Owner

Thank you for this PR.

I tested killed builds, update and status on your branch and on staging.

Before I can merge it, please fix these:

  1. status no longer shows a postprocess-pending graph as incomplete (code_review_graph/tools/build.py:36-43). build_was_interrupted now checks only in-progress. Steps: build, commit a change, then update --skip-postprocess. Now status --json says "build_incomplete": false. test_kill_after_the_anchor_is_visible_as_an_incomplete_build now fails. It passes on staging.

  2. This also breaks repair after a killed build (code_review_graph/tools/build.py:643). Kill a build after the anchor is written. Commit one changed file, then run update. It runs an incremental update, not a full rebuild. Flows and communities stay at 0. A clean build has 24 of each. The state still says complete, so status shows no problem.

  3. A partial --skip-postprocess update still marks the graph complete (code_review_graph/tools/build.py:810-814). Example: change a.py and b.py, and make b.py fail to parse. a.py is saved, but its new name is not in search. Please keep postprocess-pending when any rows changed.

After you push the fix, I will test it again.

azizur100389 pushed a commit to azizur100389/code-review-graph that referenced this pull request Oct 15, 2026
- incremental update that re-parses files reports the skip, including a
  repo path with spaces and non-ASCII characters
- repeated build/update requests report every time and never create the
  embeddings table
- no-change update reports the skip at postprocess none/minimal/full
  (guards the tirth8205#1105 no-op rewrite in tools/build.py)
- interrupted-build repair reports the skip
- postprocess with an empty embeddings table keeps status ok
- skip, then embed, then refresh reports embeddings_refreshed and no notice
- has_embeddings: no table, empty table, current and legacy rows
- legacy index without provider identity fails the refresh and stops watch;
  an empty legacy table is a skip
- watch exempts only the exact notice (duplicates ok; half-pair, altered
  text still fail); None/non-dict results are ignored
- real MCP tools and CLI build/update/postprocess/--quiet report the
  notice, exit cleanly and never resolve a provider

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

code-review-graph review

Overall risk: 0.70 (HIGH) — 7 changed function(s)/class(es), 3 affected flow(s), 1 test gap(s) (0 with no tested caller found, 1 reached only through a caller)

Risk-scored changes

Risk Level Symbol Location Tested
0.70 high code_review_graph/tools/build.py::build_was_interrupted code_review_graph/tools/build.py:37 indirect
0.55 medium code_review_graph/tools/build.py::build_or_update_graph code_review_graph/tools/build.py:603 yes
0.05 low tests/test_freshness_metadata.py::test_noop_update_repairs_fts_drift_without_rebuilding tests/test_freshness_metadata.py:85 (test)
0.05 low tests/test_freshness_metadata.py::test_skip_postprocess_marks_pending_and_noop_update_repairs_fts tests/test_freshness_metadata.py:134 (test)
0.05 low tests/test_freshness_metadata.py::test_pending_changed_file_update_rebuilds_all_derived_data tests/test_freshness_metadata.py:176 (test)
0.05 low tests/test_freshness_metadata.py::test_partial_skip_postprocess_update_keeps_pending_state tests/test_freshness_metadata.py:220 (test)
0.05 low tests/test_freshness_metadata.py::test_status_json_marks_postprocess_pending_as_incomplete tests/test_freshness_metadata.py:246 (test)

Affected execution flows

  • build_or_update_graph_tool — criticality 0.73, 620 node(s) across 24 file(s)
  • main — criticality 0.72, 925 node(s) across 40 file(s)
  • run_postprocess_tool — criticality 0.63, 54 node(s) across 13 file(s)

Reached only through a caller

No test names these directly, but a tested caller reaches them along the call path shown. That is a path in the call graph, not a record of execution -- the caller's tests may never take this branch. Treat it as where to look, not as coverage: these count as gaps and are scored as untested.

  • code_review_graph/tools/build.py::build_was_interrupted (code_review_graph/tools/build.py:37) — via code_review_graph/tools/build.py::build_or_update_graph, 1 hop(s)

Powered by code-review-graph — local-first analysis; no code leaves the CI runner.

@Ha1baraA11

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed report. The current head d099d6d addresses each case:

  • status treats postprocess-pending as incomplete.
  • An update after an interrupted build triggers a full rebuild, restoring flows and communities.
  • If parsing partially fails after files are written while postprocess="none", the build remains pending so stale derived data is not reported as complete.

I reran the three targeted freshness regressions (3 passed) and both opt-in interruption and recovery tests (2 passed). The required lint, type-check, security, schema-sync, and Python 3.10–3.13 checks are green on this head; Upgrade path from released versions is skipped. The full local test suite did not complete: two runs exited with code 139 at different tests, so I am not reporting a full-suite pass.

Please take another look at this head when convenient.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Pending-state recovery can skip unfinished language resolvers and mark the graph complete despite missing cross-file relationships.

3 open issues
What changed in this PR

This PR addresses #1104 by preserving pending derived-data state when post-processing is skipped and enabling later updates to repair stale full-text search (FTS) data without rebuilding source files.

Changes:

  • Keep skipped post-processing visibly pending.
  • Reconcile FTS drift on no-op updates, including older graphs marked complete.
  • Add regression coverage for pending-state recovery, derived-data repair, and incomplete status reporting.
File Description
tests/​test_freshness_metadata.py Adds regression tests for FTS repair and pending-state handling.
code_review_graph/​tools/​build.py Updates recovery, no-op reconciliation, and build-state transitions.

Review effort: Balanced

# but only rebuild source files when storage itself was interrupted.
interrupted = build_was_interrupted(store)
if interrupted and not full_rebuild:
if not graph_contents_are_complete(read_build_state(store)) and not full_rebuild:
Comment on lines +757 to +760
warnings = _run_postprocess(
store,
build_result,
postprocess_level,
Comment on lines +757 to 771
warnings = _run_postprocess(
store,
build_result,
postprocess_level,
full_rebuild=False,
changed_files=None,
repo_root=str(root),
)
if warnings:
build_result["warnings"] = warnings
if build_result.get("postprocess_failed"):
build_result["status"] = "partial"
if not build_result.get("postprocess_contended"):
store.set_metadata(BUILD_STATE_KEY, BUILD_COMPLETE)
if embedding_provider and embedding_model:
@tirth8205 tirth8205 closed this pull request by merging all changes into tirth8205:staging in 0160eb0 Nov 6, 2026
gowrishacv pushed a commit to gowrishacv/code-review-graph that referenced this pull request Nov 6, 2026
…view of tirth8205#1105)

A no-op update on a complete graph now checks FTS only, and does not rewrite
every edge. A pending no-op update forwards the embedding refresh. full_build
runs its language resolvers before it records postprocess-pending, so a build
stopped there gets a full rebuild.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gowrishacv pushed a commit to gowrishacv/code-review-graph that referenced this pull request Nov 6, 2026
# Conflicts:
#	code_review_graph/tools/build.py
gowrishacv pushed a commit to gowrishacv/code-review-graph that referenced this pull request Nov 6, 2026
Ruby methods always carry their full lexical scope (tirth8205#933), so the leaf
collision index from tirth8205#1087 now covers Python only, and the docs and tests say
so. Generators are not component wrappers' functions (tirth8205#972 with tirth8205#1131). Partial
graph coverage counts tracked files only (tirth8205#881 with tirth8205#1094). A pending marker
recomputes all derived data (tirth8205#1105), so that test starts from a finished graph,
the deleted-seed case now passes (tirth8205#1062 with tirth8205#673), and hooks.json keeps its
events under a hooks key (tirth8205#1102 with tirth8205#1126).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

4 participants