diff --git a/gitnexus/src/core/lbug/lbug-adapter.ts b/gitnexus/src/core/lbug/lbug-adapter.ts index 83b13d5f95..573154812b 100644 --- a/gitnexus/src/core/lbug/lbug-adapter.ts +++ b/gitnexus/src/core/lbug/lbug-adapter.ts @@ -2294,6 +2294,11 @@ export const deleteNodesForFiles = async ( if (!conn) { throw new Error('LadybugDB not initialized. Call initLbug first.'); } + // A persisted HNSW index makes even ordinary CodeEmbedding DELETEs depend + // on VECTOR in every new connection. Keep this mutation path offline-only: + // if VECTOR is unavailable, the delete below still works for an unindexed + // table and surfaces LadybugDB's real error for an indexed one. + await loadVectorExtension(undefined, { policy: 'load-only' }); const targetConn = conn; let warnedMissingEmbeddingTable = false; for (let i = 0; i < filePaths.length; i += DELETE_FILES_CHUNK_SIZE) { diff --git a/gitnexus/test/integration/lbug-vector-extension.test.ts b/gitnexus/test/integration/lbug-vector-extension.test.ts index 5d0e18e013..17d03a26d7 100644 --- a/gitnexus/test/integration/lbug-vector-extension.test.ts +++ b/gitnexus/test/integration/lbug-vector-extension.test.ts @@ -154,6 +154,37 @@ withTestLbugDB('vector-index-creation', (handle) => { const rows = await adapter.executeQuery('CALL SHOW_INDEXES() RETURN *'); expect(rows.filter((row: any) => row.index_name === 'code_embedding_idx')).toHaveLength(1); }); + + it('deletes embedding rows from a persisted HNSW index after close and re-open', async () => { + const adapter = await import('../../src/core/lbug/lbug-adapter.js'); + const { batchInsertEmbeddings } = + await import('../../src/core/embeddings/embedding-pipeline.js'); + const { EMBEDDING_DIMS } = await import('../../src/core/lbug/schema.js'); + const filePath = 'src/vector-reopen-delete.ts'; + const nodeId = `Function:${filePath}:target:1`; + + await adapter.executeQuery( + `CREATE (:Function {id: '${nodeId}', name: 'target', filePath: '${filePath}', startLine: 1, endLine: 3, isExported: true, content: '', description: ''})`, + ); + await batchInsertEmbeddings(adapter.executeWithReusedStatement, [ + { + nodeId, + chunkIndex: 0, + startLine: 1, + endLine: 3, + embedding: new Array(EMBEDDING_DIMS).fill(0), + }, + ]); + await adapter.createVectorIndex(); + await adapter.closeLbug(); + await adapter.initLbug(handle.dbPath); + + await expect(adapter.deleteNodesForFiles([filePath])).resolves.toBeUndefined(); + const rows = (await adapter.executeQuery( + `MATCH (e:CodeEmbedding) WHERE e.nodeId = '${nodeId}' RETURN count(e) AS count`, + )) as Array<{ count: number | bigint }>; + expect(Number(rows[0]?.count ?? 0)).toBe(0); + }); }); });