Skip to content

fix(native): resolve version-mismatch that broke incremental builds#930

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

fix(native): resolve version-mismatch that broke incremental builds#930
carlos-alm wants to merge 2 commits intomainfrom
fix/native-incremental-version-mismatch

Conversation

@carlos-alm
Copy link
Copy Markdown
Contributor

Summary

  • Fixes a version ping-pong between JS (CODEGRAPH_VERSION from package.json) and Rust (CARGO_PKG_VERSION from Cargo.toml) that silently forced every native incremental build to be a full rebuild
  • When npm and crate versions diverge, setBuildMeta now writes the Rust addon version as codegraph_version, and checkEngineSchemaMismatch checks against the engine version — both sides agree

Root cause

Two independent version-mismatch checks fought over codegraph_version in build_meta:

  1. JS side (checkEngineSchemaMismatch): compared DB value against JS CODEGRAPH_VERSION (npm package version, e.g. 3.9.3)
  2. Rust side (check_version_mismatch): compared DB value against CARGO_PKG_VERSION (Rust crate version, e.g. 3.9.2)

After a native build, JS wrote 3.9.3 → next build, Rust saw 3.9.3 ≠ 3.9.2 → forced full rebuild. If Rust wrote 3.9.2, JS saw 3.9.2 ≠ 3.9.3 → set forceFullRebuild=true → skipped native orchestrator → fell through to JS pipeline for a full rebuild.

Benchmark results (before → after)

Metric Before After Improvement
Native no-op rebuild 5,825 ms 40 ms 145x faster
Native 1-file rebuild 6,349 ms 1,365 ms 4.6x faster

Test plan

  • Verified codegraph_version in build_meta matches Rust addon version after native build
  • Verified native no-op rebuild correctly returns early exit (40ms)
  • Verified native 1-file rebuild is properly incremental (75 functions analyzed, not 3,273)
  • Verified WASM engine unaffected (uses CODEGRAPH_VERSION as before)
  • Full benchmark suite passes with both engines

Related: #929 (edge accumulation in native incremental — separate issue)

…l builds

When JS and Rust crate versions diverge (e.g. npm 3.9.3 vs Cargo 3.9.2),
the JS side wrote CODEGRAPH_VERSION (npm) into build_meta while the Rust
side checked it against CARGO_PKG_VERSION (crate). This mismatch caused
every native incremental build to be promoted to a full rebuild — the
version check in setupPipeline set forceFullRebuild=true, and the Rust
check_version_mismatch returned force_full_rebuild=true.

Fix: when the native engine is active, use the Rust addon version
(ctx.engineVersion) consistently for both writing and checking
codegraph_version in build_meta.

Before: native no-op rebuild ~5,800ms (full rebuild every time)
After:  native no-op rebuild ~40ms (proper early exit)

Closes #929 (related: edge accumulation in native incremental)

Impact: 2 functions changed, 7 affected
Impact: 1 functions changed, 5 affected
@claude
Copy link
Copy Markdown

claude bot commented Apr 15, 2026

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


PR Review: Fix Native Incremental Version Mismatch

Root Cause Analysis

Problem Being Solved: Version ping-pong between JS (CODEGRAPH_VERSION from package.json = 3.9.3) and Rust (CARGO_PKG_VERSION from Cargo.toml) was forcing every native incremental build to be a full rebuild, causing severe performance degradation.

Why This Approach: The fix ensures both sides of the version check use consistent values. When native engine builds, it now writes the Rust addon version to codegraph_version metadata, and the JS check compares against the same version (effective version = engine version for native builds).

Risk Assessment: ✅ Low risk. The changes are surgical and well-isolated. The logic preserves existing WASM behavior while fixing the native-specific issue.

Backlog Compliance

  • Zero-dep: ✓ — No new dependencies added
  • Foundation-aligned: ✓ — Improves core performance, maintains local-first operation
  • Problem-fit: 5 — Directly addresses performance issues that affect AI agent workflows by preventing unnecessary full rebuilds
  • Breaking: No — Preserves all existing behavior, purely performance improvement
  • Tier: 1

Critical Concerns

None identified. This is a well-executed performance fix.

Code Quality Analysis ⭐⭐⭐⭐

Positive aspects:

  1. Surgical precision: Only 2 locations modified with clear, documented logic
  2. Excellent documentation: Both changes include detailed comments explaining the version mismatch problem
  3. Consistent logic: effectiveVersion calculation (lines 96-97) matches setBuildMeta usage (line 650)
  4. Performance impact: 145x improvement for no-op rebuilds, 4.6x for incremental builds
  5. Fallback safety: Gracefully handles cases where ctx.engineVersion is undefined

Technical correctness:

  • pipeline.ts:96-97: Conditional logic correctly uses native version when available, falls back to JS version
  • pipeline.ts:650: Consistent with the check logic, prevents version fights
  • No side effects: Changes are isolated to version checking, don't affect build logic
  • Backward compatibility: WASM engine behavior unchanged

Security: No security implications. Changes only affect version metadata consistency.

Test Coverage

The existing test in tests/unit/native-db-version-check.test.ts validates core version checking functionality. While it doesn't specifically test the new effective version logic, the change is minimal and well-contained.

Recommendation: Consider adding a specific test case for the native vs JS version scenario, but this is not blocking.

Performance Validation

The benchmark results are impressive and consistent with the described fix:

  • Native no-op rebuild: 5,825ms → 40ms (145x improvement)
  • Native 1-file rebuild: 6,349ms → 1,365ms (4.6x improvement)

These improvements directly address the core issue of AI coding assistants being slowed down by unnecessary full rebuilds.

Final Recommendation

  • Rating: ⭐⭐⭐⭐ (4/5)
  • Action: APPROVE
  • Reasoning: This is a high-quality fix that addresses a real performance bug with minimal code changes, excellent documentation, and significant measured improvements. The approach is technically sound and preserves all existing behavior while eliminating the version ping-pong issue.

The only reason this doesn't get 5 stars is the lack of specific test coverage for the new logic, but the risk is low given the surgical nature of the changes.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 15, 2026

Greptile Summary

This PR fixes the version ping-pong between the JS pipeline (CODEGRAPH_VERSION) and the Rust orchestrator (CARGO_PKG_VERSION) that caused every native incremental build to silently become a full rebuild when the two versions diverge. The tryNativeOrchestrator path is correctly fixed — it now writes ctx.engineVersion as codegraph_version, and checkEngineSchemaMismatch now compares against the same value.

  • Incomplete fix: stages/finalize.ts:persistBuildMetadata still writes CODEGRAPH_VERSION as codegraph_version unconditionally. Whenever the JS-pipeline fallback runs (schema migration, engine switch, any other forceFullRebuild trigger), it persists the npm version instead of the Rust crate version. On the next build, checkEngineSchemaMismatch sees prevVersion = CODEGRAPH_VERSION ≠ effectiveVersion = ctx.engineVersion, forces another full rebuild via JS, overwrites with CODEGRAPH_VERSION again, and loops forever — a tighter regression than the pre-PR two-build cycle.

Confidence Score: 4/5

Safe to merge for the primary use case, but the incomplete fix in finalize.ts creates a new infinite-loop regression for users with diverging npm/crate versions who encounter any JS-pipeline full rebuild trigger.

The core fix is correct and delivers the 145× speedup for the normal incremental path. However, stages/finalize.ts was not updated alongside pipeline.ts, leaving a symmetric inconsistency that turns any post-schema-change build into an infinite full-rebuild loop when versions diverge — a concrete defect in the changed code path.

src/domain/graph/builder/stages/finalize.tspersistBuildMetadata must also use ctx.engineVersion for codegraph_version when the native engine is active.

Important Files Changed

Filename Overview
src/domain/graph/builder/pipeline.ts Fixes the native-orchestrator/JS version ping-pong correctly, but leaves finalize.persistBuildMetadata writing CODEGRAPH_VERSION unconditionally — causing a tighter rebuild loop whenever the JS fallback path runs a full rebuild with diverging npm/crate versions.

Sequence Diagram

sequenceDiagram
    participant JS as JS checkEngineSchemaMismatch
    participant NO as tryNativeOrchestrator
    participant FIN as finalize.persistBuildMetadata
    participant DB as build_meta (codegraph_version)

    Note over JS,DB: Normal incremental path (FIXED)
    DB-->>JS: prevVersion = 3.9.2
    JS->>JS: effectiveVersion = ctx.engineVersion = 3.9.2
    JS->>NO: forceFullRebuild = false, native orchestrator runs
    NO->>DB: codegraph_version = ctx.engineVersion = 3.9.2

    Note over JS,DB: After schema migration (REGRESSION)
    DB-->>JS: prevSchema != current, forceFullRebuild = true
    JS->>NO: shouldSkipNativeOrchestrator returns forceFullRebuild
    NO-->>JS: returns undefined, JS pipeline runs
    FIN->>DB: codegraph_version = CODEGRAPH_VERSION = 3.9.3 (wrong)

    Note over JS,DB: Next build (infinite loop)
    DB-->>JS: prevVersion = 3.9.3
    JS->>JS: effectiveVersion = ctx.engineVersion = 3.9.2
    JS->>JS: 3.9.3 != 3.9.2, forceFullRebuild = true
    JS->>NO: shouldSkipNativeOrchestrator returns forceFullRebuild
    NO-->>JS: returns undefined, JS pipeline runs again
    FIN->>DB: codegraph_version = 3.9.3 (loop repeats)
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "style: fix biome formatting in pipeline...." | Re-trigger Greptile

Comment on lines +96 to +97
const effectiveVersion =
ctx.engineName === 'native' && ctx.engineVersion ? ctx.engineVersion : CODEGRAPH_VERSION;
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.

P1 finalize.ts still writes CODEGRAPH_VERSION, re-creating the loop

checkEngineSchemaMismatch now correctly compares against ctx.engineVersion when native is active, but finalize.persistBuildMetadata (stages/finalize.ts:89–101) still writes CODEGRAPH_VERSION as codegraph_version unconditionally. This leaves an inconsistency: any time the JS-pipeline fallback runs a full rebuild (triggered by schema migration, engine switch, etc.), it persists the npm package version, and on the very next build this check sees prevVersion = CODEGRAPH_VERSION ≠ effectiveVersion = ctx.engineVersion, sets forceFullRebuild = true, skips the native orchestrator again, and the cycle repeats indefinitely. Before this PR the same schema-change scenario produced a two-build cycle via the Rust orchestrator; after the PR, this check closes the loop entirely in JS — every subsequent build is a wasted full rebuild.

The symmetric fix is needed in finalize.ts:

// persistBuildMetadata in finalize.ts
const codeVersionToWrite =
  ctx.engineName === 'native' && ctx.engineVersion ? ctx.engineVersion : CODEGRAPH_VERSION;
// …then use codeVersionToWrite for the codegraph_version field in both branches

Fix in Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

2 functions changed7 callers affected across 5 files

  • checkEngineSchemaMismatch in src/domain/graph/builder/pipeline.ts:68 (5 transitive callers)
  • tryNativeOrchestrator in src/domain/graph/builder/pipeline.ts:585 (5 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