From bacb00bfde8f5dda735d0a326caaa33bd2da5bbf Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:35:37 -0600 Subject: [PATCH 1/4] =?UTF-8?q?fix(build):=20native=20incremental=20rebuil?= =?UTF-8?q?d=20broken=20=E2=80=94=20no-op=20takes=205.8s=20instead=20of=20?= =?UTF-8?q?200ms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Cargo.lock | 2 +- crates/codegraph-core/src/build_pipeline.rs | 6 +++- src/domain/graph/builder/pipeline.ts | 33 +++++++++++++-------- src/domain/graph/builder/stages/finalize.ts | 9 ++++-- tests/integration/build.test.ts | 13 +++++--- 5 files changed, 43 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9feb0b91..610492c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,7 +66,7 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "codegraph-core" -version = "3.9.1" +version = "3.9.3" dependencies = [ "ignore", "napi", diff --git a/crates/codegraph-core/src/build_pipeline.rs b/crates/codegraph-core/src/build_pipeline.rs index 53a58f14..1512560f 100644 --- a/crates/codegraph-core/src/build_pipeline.rs +++ b/crates/codegraph-core/src/build_pipeline.rs @@ -723,7 +723,11 @@ fn check_version_mismatch(conn: &Connection) -> bool { return true; } } - if let Some(prev_version) = get_meta("codegraph_version") { + // Compare against engine_version (the addon's own version), not + // codegraph_version (the npm package version). The JS post-processing + // overwrites codegraph_version with the npm version, which may differ + // from CARGO_PKG_VERSION — causing a perpetual full-rebuild loop (#928). + if let Some(prev_version) = get_meta("engine_version") { if prev_version != current_version { return true; } diff --git a/src/domain/graph/builder/pipeline.ts b/src/domain/graph/builder/pipeline.ts index 0d85751b..d00d32ff 100644 --- a/src/domain/graph/builder/pipeline.ts +++ b/src/domain/graph/builder/pipeline.ts @@ -88,12 +88,20 @@ function checkEngineSchemaMismatch(ctx: PipelineContext): void { ); ctx.forceFullRebuild = true; } - const prevVersion = meta('codegraph_version'); - if (prevVersion && prevVersion !== CODEGRAPH_VERSION) { - info( - `Codegraph version changed (${prevVersion} → ${CODEGRAPH_VERSION}), promoting to full rebuild.`, - ); - ctx.forceFullRebuild = true; + // 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; + } } } @@ -630,15 +638,16 @@ async function tryNativeOrchestrator( const p = result.phases; - // Sync build_meta so JS-side version/engine checks work on next build. + // The Rust orchestrator already persists engine, engine_version, + // codegraph_version, node_count, edge_count, and last_build. + // Only write fields Rust doesn't set, plus built_at for JS callers. + // IMPORTANT: Do NOT overwrite codegraph_version here — Rust writes + // CARGO_PKG_VERSION and check_version_mismatch compares against it. + // Overwriting with CODEGRAPH_VERSION (npm version) when they differ + // causes a perpetual full-rebuild loop (#928). setBuildMeta(ctx.db, { - engine: ctx.engineName, - engine_version: ctx.engineVersion || '', - codegraph_version: CODEGRAPH_VERSION, schema_version: String(ctx.schemaVersion), built_at: new Date().toISOString(), - node_count: String(result.nodeCount ?? 0), - edge_count: String(result.edgeCount ?? 0), }); info( diff --git a/src/domain/graph/builder/stages/finalize.ts b/src/domain/graph/builder/stages/finalize.ts index 857bbf40..73e4e811 100644 --- a/src/domain/graph/builder/stages/finalize.ts +++ b/src/domain/graph/builder/stages/finalize.ts @@ -82,11 +82,16 @@ function persistBuildMetadata( const useNativeDb = ctx.engineName === 'native' && !!ctx.nativeDb; if (!ctx.isFullBuild && ctx.allSymbols.size <= 3) return; try { + // engine_version must be the actual engine version (addon version for native, + // npm version for WASM) — not always CODEGRAPH_VERSION. The Rust orchestrator's + // check_version_mismatch compares engine_version against CARGO_PKG_VERSION, + // so writing the npm version here would cause perpetual full rebuilds (#928). + const engineVer = ctx.engineVersion || CODEGRAPH_VERSION; if (useNativeDb) { ctx.nativeDb!.setBuildMeta( Object.entries({ engine: ctx.engineName, - engine_version: CODEGRAPH_VERSION, + engine_version: engineVer, codegraph_version: CODEGRAPH_VERSION, schema_version: String(ctx.schemaVersion), built_at: buildNow.toISOString(), @@ -97,7 +102,7 @@ function persistBuildMetadata( } else { setBuildMeta(ctx.db, { engine: ctx.engineName, - engine_version: CODEGRAPH_VERSION, + engine_version: engineVer, codegraph_version: CODEGRAPH_VERSION, schema_version: String(ctx.schemaVersion), built_at: buildNow.toISOString(), diff --git a/tests/integration/build.test.ts b/tests/integration/build.test.ts index 218d90c8..d70d2731 100644 --- a/tests/integration/build.test.ts +++ b/tests/integration/build.test.ts @@ -490,17 +490,22 @@ describe('version/engine mismatch auto-promotes to full rebuild', () => { ); db2.close(); - // codegraph_version must match the npm package version const pkg = JSON.parse( fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf8'), ); - expect(meta.codegraph_version).toBe(pkg.version); // engine must be either 'native' or 'wasm' (not empty, not stale) expect(['native', 'wasm']).toContain(meta.engine); - // engine_version must equal the npm package version (#751: was using Rust crate version) - expect(meta.engine_version).toBe(pkg.version); + // engine_version must be a valid semver string. For native engine, this is + // the addon version (CARGO_PKG_VERSION); for WASM, the npm package version. + // They may differ when the addon hasn't been republished yet (#928). + 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+/); // built_at must be a valid ISO timestamp from the current build expect(new Date(meta.built_at).getTime()).toBeGreaterThan(0); From 2a8f40e40b06bf9fb3d4860e1eb728293ce6380d Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:36:08 -0600 Subject: [PATCH 2/4] fix(test): remove unused pkg variable from build metadata test --- tests/integration/build.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/integration/build.test.ts b/tests/integration/build.test.ts index d70d2731..b90b4ced 100644 --- a/tests/integration/build.test.ts +++ b/tests/integration/build.test.ts @@ -490,10 +490,6 @@ describe('version/engine mismatch auto-promotes to full rebuild', () => { ); db2.close(); - const pkg = JSON.parse( - fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf8'), - ); - // engine must be either 'native' or 'wasm' (not empty, not stale) expect(['native', 'wasm']).toContain(meta.engine); From c494abddfbad8433687d242e3a3f5762b175b3c2 Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:44:39 -0600 Subject: [PATCH 3/4] fix(test): add end anchor to semver regex assertions (#928) --- tests/integration/build.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/build.test.ts b/tests/integration/build.test.ts index b90b4ced..e75ae447 100644 --- a/tests/integration/build.test.ts +++ b/tests/integration/build.test.ts @@ -496,12 +496,12 @@ describe('version/engine mismatch auto-promotes to full rebuild', () => { // engine_version must be a valid semver string. For native engine, this is // the addon version (CARGO_PKG_VERSION); for WASM, the npm package version. // They may differ when the addon hasn't been republished yet (#928). - expect(meta.engine_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+/); + expect(meta.codegraph_version).toMatch(/^\d+\.\d+\.\d+$/); // built_at must be a valid ISO timestamp from the current build expect(new Date(meta.built_at).getTime()).toBeGreaterThan(0); From 3743b9c1d89c8089d53727aea21e3519a0859fdc Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:44:49 -0600 Subject: [PATCH 4/4] fix(build): re-check version gate on native orchestrator fallback (#928) Impact: 1 functions changed, 6 affected --- src/domain/graph/builder/pipeline.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/domain/graph/builder/pipeline.ts b/src/domain/graph/builder/pipeline.ts index d00d32ff..96eee63c 100644 --- a/src/domain/graph/builder/pipeline.ts +++ b/src/domain/graph/builder/pipeline.ts @@ -825,7 +825,19 @@ export async function buildGraph( if (nativeResult) return nativeResult; } catch (err) { warn(`Native build orchestrator failed, falling back to JS pipeline: ${toErrorMessage(err)}`); - // Fall through to JS pipeline + // The version gate in checkEngineSchemaMismatch was skipped because + // nativeAvailable was true. Now that we're falling back to the JS + // pipeline, perform the codegraph_version check here so a version + // bump still promotes to a full rebuild (#928). + if (ctx.incremental && !ctx.forceFullRebuild) { + const prevVersion = getBuildMeta(ctx.db, 'codegraph_version'); + if (prevVersion && prevVersion !== CODEGRAPH_VERSION) { + info( + `Codegraph version changed (${prevVersion} → ${CODEGRAPH_VERSION}), promoting to full rebuild.`, + ); + ctx.forceFullRebuild = true; + } + } } await runPipelineStages(ctx);