From 8d38cc99fa6c237c925fdf052735cb13470ac856 Mon Sep 17 00:00:00 2001 From: jonasvanderhaegen-xve <> Date: Tue, 14 Apr 2026 12:59:14 +0200 Subject: [PATCH] fix(embeddings): use MERGE instead of CREATE for CodeEmbedding inserts CREATE fails with duplicate PK when a CodeEmbedding node already exists, which happens when: - A PostToolUse hook triggers a concurrent gitnexus analyze during an active analyze run (git commits fire the hook) - A partial prior run left some embeddings in the DB before a crash Switching to MERGE makes the insert idempotent: existing embeddings are updated in place, new ones are created, no PK violations. Fixes: #822 --- gitnexus/src/core/embeddings/embedding-pipeline.ts | 4 ++-- gitnexus/src/core/run-analyze.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gitnexus/src/core/embeddings/embedding-pipeline.ts b/gitnexus/src/core/embeddings/embedding-pipeline.ts index d3dc0854e..cb1949144 100644 --- a/gitnexus/src/core/embeddings/embedding-pipeline.ts +++ b/gitnexus/src/core/embeddings/embedding-pipeline.ts @@ -100,8 +100,8 @@ const batchInsertEmbeddings = async ( ) => Promise, updates: Array<{ id: string; embedding: number[] }>, ): Promise => { - // INSERT into separate embedding table - much more memory efficient! - const cypher = `CREATE (e:CodeEmbedding {nodeId: $nodeId, embedding: $embedding})`; + // MERGE instead of CREATE — idempotent, handles concurrent analyzes and partial prior runs + const cypher = `MERGE (e:CodeEmbedding {nodeId: $nodeId}) SET e.embedding = $embedding`; const paramsList = updates.map((u) => ({ nodeId: u.id, embedding: u.embedding })); await executeWithReusedStatement(cypher, paramsList); }; diff --git a/gitnexus/src/core/run-analyze.ts b/gitnexus/src/core/run-analyze.ts index f7b662705..07fb8ab69 100644 --- a/gitnexus/src/core/run-analyze.ts +++ b/gitnexus/src/core/run-analyze.ts @@ -222,7 +222,7 @@ export async function runFullAnalysis( const paramsList = batch.map((e) => ({ nodeId: e.nodeId, embedding: e.embedding })); try { await executeWithReusedStatement( - `CREATE (e:CodeEmbedding {nodeId: $nodeId, embedding: $embedding})`, + `MERGE (e:CodeEmbedding {nodeId: $nodeId}) SET e.embedding = $embedding`, paramsList, ); } catch {