Skip to content

fix(build): native incremental rebuild perpetual full-rebuild loop#928

Open
carlos-alm wants to merge 2 commits intomainfrom
fix/native-incremental-rebuild
Open

fix(build): native incremental rebuild perpetual full-rebuild loop#928
carlos-alm wants to merge 2 commits intomainfrom
fix/native-incremental-rebuild

Conversation

@carlos-alm
Copy link
Copy Markdown
Contributor

Summary

  • Root cause: Version mismatch between npm package (3.9.3) and native addon (3.9.2) caused the Rust check_version_mismatch to force a full rebuild on every build. The JS post-processing overwrote codegraph_version in build_meta with the npm version ("3.9.3"), but the Rust addon compared it against CARGO_PKG_VERSION ("3.9.2") — always mismatching.
  • Impact: No-op native rebuild took 5.8s (full rebuild) instead of ~200ms (early exit). Affected every native build since 3.9.3 npm release.
  • Fix: Three-pronged — Rust checks engine_version instead of codegraph_version; JS stops overwriting Rust-written metadata after native orchestrator; JS skips its own version check when native engine handles it.

Before / After

Metric Before After
No-op native rebuild 5.8s (full rebuild) 214ms (early exit)
Log output "16853 nodes, 33654 edges" "No changes detected"

Test plan

  • tests/integration/build.test.ts — build metadata assertions updated
  • tests/integration/incremental-parity.test.ts — all 26 tests pass
  • tests/builder/ — all builder unit tests pass
  • Rust cargo check passes
  • Manual verification: fresh build + two consecutive no-op rebuilds confirm early exit at ~200ms

…ead of 200ms

Root cause: version mismatch between npm package (3.9.3) and native addon
(3.9.2) caused perpetual full rebuilds. The Rust check_version_mismatch
read codegraph_version from build_meta (overwritten by JS to "3.9.3") and
compared it to CARGO_PKG_VERSION ("3.9.2") — always mismatching.

Fix (three-pronged):
- Rust: check engine_version instead of codegraph_version for rebuild
  decisions — engine_version is consistently the addon's own version
- JS pipeline: stop overwriting Rust-written build_meta fields after native
  orchestrator completes; skip codegraph_version mismatch check when native
  engine is available (Rust handles its own version check)
- JS finalize: write actual engine version (ctx.engineVersion) to
  engine_version instead of always using CODEGRAPH_VERSION (npm version)

Impact: 4 functions changed, 10 affected
@claude
Copy link
Copy Markdown

claude bot commented Apr 15, 2026

Claude finished @carlos-alm's task —— View job


Reviewing this PR for native incremental rebuild fix...

Review Progress

  • Phase 1: Root cause analysis
  • Phase 2: Configuration change detection
  • Phase 3: Test integrity verification
  • Phase 4: Code quality review
  • Phase 5: Backlog compliance assessment
  • Final recommendation

Phase 1: Root Cause Analysis ✅

Problem Being Solved: Version mismatch between npm package (3.9.3) and native Rust addon (3.9.2) causing perpetual full rebuilds instead of incremental builds. The Rust check_version_mismatch function was comparing codegraph_version (overwritten by JS to npm version) against CARGO_PKG_VERSION, always finding a mismatch.

Root Cause is Sound: This is fixing a real performance issue, not hiding symptoms. The 5.8s → 214ms improvement is substantial and addresses actual broken incremental builds.

Why This Approach: Three-pronged fix is logical:

  1. Rust checks engine_version instead of codegraph_version (build_pipeline.rs:730)
  2. JS stops overwriting Rust metadata after native orchestrator (pipeline.ts:644-647)
  3. JS skips its own version check when native handles it (finalize.ts:85-89)

Phase 2: Configuration Change Detection ✅

No High-Risk Configuration Changes: This PR contains no linting rule relaxation, test timeout increases, or build configuration modifications. Changes are purely logic fixes to version comparison behavior.

Phase 3: Test Integrity Verification ✅

Test Changes Are Sound: The updated assertions in build.test.ts correctly relax overly strict version requirements that were breaking due to the npm/addon version gap. Changed from exact pkg.version match to regex pattern matching valid semver, which is more robust and aligns with the fix's intent.

Phase 4: Code Quality Review ✅

Strong Implementation:

  • Clear separation of concerns between engine_version (addon version) and codegraph_version (npm version)
  • Comprehensive comments explaining the fix and referencing the issue number
  • Logical conditional checks prevent JS from overriding Rust metadata
  • Cargo.lock update shows version bump (3.9.1 → 3.9.3)

No Security/Performance Concerns: Changes are purely metadata handling with no external inputs or performance regressions.

Phase 5: Backlog Compliance Assessment ✅

Zero-dep: ✓ — No new runtime dependencies added
Foundation-aligned: ✓ — Improves core performance without violating any principles
Problem-fit: 5 — Directly addresses core performance issue that blocks efficient incremental builds
Breaking: No — Purely internal fix, no API changes

Tier: 1 (zero-dep ✓ + foundation-aligned ✓)

Root Cause Analysis

Problem Being Solved: Version mismatch between npm package and native addon causing perpetual 5.8s full rebuilds instead of 214ms incremental builds
Why This Approach: Sound three-pronged fix targeting the exact mismatch in version comparison logic
Risk Assessment: Very low risk - focused fix with comprehensive test coverage

Backlog Compliance

  • Zero-dep: ✓ — no new deps
  • Foundation-aligned: ✓ — improves core performance
  • Problem-fit: 5 — directly fixes core incremental build performance
  • Breaking: No — internal metadata handling only
  • Tier: 1

Final Recommendation

  • Rating: ⭐⭐⭐⭐⭐ (5/5)
  • Action: APPROVE
  • Reasoning: Excellent fix for a critical performance regression. Clean implementation with thorough documentation, proper test updates, and addresses the exact root cause without introducing risk.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 15, 2026

Greptile Summary

This PR fixes a perpetual full-rebuild loop where the JS post-build step overwrote codegraph_version with the npm version after every successful native orchestrator run. Because check_version_mismatch in Rust compared codegraph_version against CARGO_PKG_VERSION, and those two values diverged when the npm release version advanced ahead of the Rust crate version, every subsequent native build was forced to a full rebuild instead of taking the fast incremental path. The three-part fix is: Rust now checks engine_version (which it controls) rather than codegraph_version; the JS orchestrator wrapper no longer overwrites Rust-written metadata; and the JS pipeline's version gate is suppressed when native is available (since Rust handles it). Cargo.lock is also bumped from 3.9.1 to 3.9.3 to align CARGO_PKG_VERSION with the npm release.

Confidence Score: 5/5

Safe to merge — root cause is correctly identified and fixed, all three change sites are consistent, and no correctness regressions were found.

All findings are P2. The test regex missing $ is cosmetic. The nativeAvailable version-gate gap is a pre-existing edge case (orchestrator crash after schema check) that is not made worse by this PR and does not affect the primary or fallback paths under normal operation. The fix itself is minimal, well-commented, and consistent across Rust, JS orchestrator wrapper, and JS finalize stage.

No files require special attention; the minor test assertion at tests/integration/build.test.ts:499 is the only item worth a quick look.

Important Files Changed

Filename Overview
crates/codegraph-core/src/build_pipeline.rs Changes check_version_mismatch to compare engine_version (Rust-owned) instead of codegraph_version (JS-overwritten); fix is minimal and correct
src/domain/graph/builder/pipeline.ts Two changes: JS version check gated on !ctx.nativeAvailable; tryNativeOrchestrator no longer overwrites Rust-written metadata. A narrow gap exists where native-available but orchestrator-failed builds skip the version check in JS.
src/domain/graph/builder/stages/finalize.ts JS finalize now writes `engine_version = ctx.engineVersion
tests/integration/build.test.ts Test assertions relaxed from exact npm-version equality to semver-prefix regex; the regex lacks an end anchor, and the semantic invariant it guards is weaker than before
Cargo.lock Bumps codegraph-core from 3.9.1 to 3.9.3, syncing CARGO_PKG_VERSION with the npm release to eliminate any residual mismatch

Sequence Diagram

sequenceDiagram
    participant JS as JS Pipeline
    participant Rust as Rust Orchestrator
    participant DB as SQLite (build_meta)

    note over JS,DB: Before fix — perpetual full-rebuild loop
    JS->>Rust: buildGraph(...)
    Rust->>DB: write engine_version = CARGO_PKG_VERSION (3.9.2)
    Rust->>DB: write codegraph_version = CARGO_PKG_VERSION (3.9.2)
    Rust-->>JS: result
    JS->>DB: overwrite codegraph_version = CODEGRAPH_VERSION (3.9.3)
    note right of DB: codegraph_version = 3.9.3
    JS->>Rust: buildGraph(...) [next build]
    Rust->>DB: read codegraph_version → 3.9.3
    note right of Rust: 3.9.3 ≠ CARGO_PKG_VERSION 3.9.2 → force full rebuild!
    Rust-->>JS: full rebuild (5.8s)

    note over JS,DB: After fix — fast incremental path
    JS->>JS: checkEngineSchemaMismatch skip version check nativeAvailable=true
    JS->>Rust: buildGraph(...)
    Rust->>DB: write engine_version = CARGO_PKG_VERSION (3.9.3)
    Rust->>DB: write codegraph_version = CARGO_PKG_VERSION (3.9.3)
    Rust-->>JS: result
    JS->>DB: write only schema_version + built_at
    JS->>Rust: buildGraph(...) [next build]
    Rust->>DB: read engine_version → 3.9.3
    note right of Rust: 3.9.3 == CARGO_PKG_VERSION 3.9.3 → early exit!
    Rust-->>JS: early-exit (214ms)
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix(test): remove unused pkg variable fr..." | Re-trigger Greptile

Comment on lines +499 to +504
expect(meta.engine_version).toMatch(/^\d+\.\d+\.\d+/);

// codegraph_version tracks the npm package version when the JS pipeline
// finalizes, or the addon version when the Rust orchestrator runs.
// Either is valid — the key invariant is it's a valid semver string.
expect(meta.codegraph_version).toMatch(/^\d+\.\d+\.\d+/);
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.

P2 Semver regex missing end anchor

Both regexes match any string that starts with a semver prefix — e.g. "3.9.3-corrupted" or "3.9.3\nunexpected" would pass. Adding $ prevents false positives and better documents the invariant.

Suggested change
expect(meta.engine_version).toMatch(/^\d+\.\d+\.\d+/);
// codegraph_version tracks the npm package version when the JS pipeline
// finalizes, or the addon version when the Rust orchestrator runs.
// Either is valid — the key invariant is it's a valid semver string.
expect(meta.codegraph_version).toMatch(/^\d+\.\d+\.\d+/);
expect(meta.engine_version).toMatch(/^\d+\.\d+\.\d+$/);
// codegraph_version tracks the npm package version when the JS pipeline
// finalizes, or the addon version when the Rust orchestrator runs.
// Either is valid — the key invariant is it's a valid semver string.
expect(meta.codegraph_version).toMatch(/^\d+\.\d+\.\d+$/);

Fix in Claude Code

Comment on lines +91 to 105
// When the native orchestrator is available, it handles its own version
// check (engine_version vs CARGO_PKG_VERSION). The JS-side codegraph_version
// may differ from the Rust addon version (npm 3.9.3 vs addon 3.9.2), so
// checking it here would force unnecessary full rebuilds and prevent the
// native orchestrator from ever running its fast incremental path (#928).
// Only check codegraph_version when falling through to the JS pipeline.
if (!ctx.nativeAvailable) {
const prevVersion = meta('codegraph_version');
if (prevVersion && prevVersion !== CODEGRAPH_VERSION) {
info(
`Codegraph version changed (${prevVersion} → ${CODEGRAPH_VERSION}), promoting to full rebuild.`,
);
ctx.forceFullRebuild = true;
}
}
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.

P2 Version gate skipped when orchestrator fails mid-run

checkEngineSchemaMismatch skips the codegraph_version check whenever ctx.nativeAvailable is true, even though the native orchestrator may subsequently fail and fall through to the JS pipeline. In that narrow scenario — native available, version bumped, orchestrator throws — the JS pipeline runs in whatever mode was already determined (potentially incremental) without the version-change promotion to full rebuild that the old code provided.

The comment says "the Rust orchestrator handles its own version check", which is true when the orchestrator succeeds. Given the warn + fall-through error handling in buildGraph, the JS pipeline does proceed without the version gate if the orchestrator throws after this check runs.

Fix in Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

4 functions changed10 callers affected across 7 files

  • check_version_mismatch in crates/codegraph-core/src/build_pipeline.rs:711 (1 transitive callers)
  • checkEngineSchemaMismatch in src/domain/graph/builder/pipeline.ts:68 (5 transitive callers)
  • tryNativeOrchestrator in src/domain/graph/builder/pipeline.ts:586 (5 transitive callers)
  • persistBuildMetadata in src/domain/graph/builder/stages/finalize.ts:76 (3 transitive callers)

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.

1 participant