From 13cbb35977b7999c9c08670e44dabecb2e48aeaf Mon Sep 17 00:00:00 2001 From: Bazhen Rzheutskii Date: Fri, 29 May 2026 00:30:43 +0400 Subject: [PATCH] fix(indexing): prevent LanceDB metadata type coercion causing full re-index on restart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LanceDB infers the value column type from the first row in _createMetadataData(). Since vector_size (1024) was stored as a number, all subsequent string values (embedding_provider, embedding_model_id) and booleans (indexing_complete) were silently coerced to NaN/0/1, corrupting metadata. On restart, _getStoredEmbeddingProfile() received NaN values, causing needsRecreation=true every time — the database was dropped and recreated, the file-hash cache cleared, and a full re-index triggered on every VS Code reopen. Store all metadata values as strings to ensure consistent column type. --- .../vector-store/lancedb-vector-store.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts b/packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts index 39bc14760d..d4433d2e30 100644 --- a/packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts +++ b/packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts @@ -136,7 +136,7 @@ export class LanceDBVectorStore implements IVectorStore { return [ { key: KEY.size, - value: this.vectorSize, + value: String(this.vectorSize), }, { key: KEY.provider, @@ -148,11 +148,11 @@ export class LanceDBVectorStore implements IVectorStore { }, { key: KEY.dimension, - value: this.profile.dimension, + value: String(this.profile.dimension), }, { key: KEY.complete, - value: false, + value: "false", }, ] } @@ -572,7 +572,7 @@ export class LanceDBVectorStore implements IVectorStore { } const metadataTable = await db.openTable(this.metadataTableName) const metadataResults = await metadataTable.query().where(`key = '${KEY.complete}'`).toArray() - const indexed = metadataResults.length > 0 ? metadataResults[0].value : false + const indexed = metadataResults.length > 0 ? String(metadataResults[0].value) === "true" : false log.info("LanceDB indexing metadata evaluated", { workspacePath: this.workspacePath, pointCount, @@ -590,14 +590,16 @@ export class LanceDBVectorStore implements IVectorStore { throw new Error(`Invalid metadata key: ${key}`) } await metadataTable.delete(`key = '${key}'`) - await metadataTable.add([{ key, value }]) + // All values must be strings to prevent LanceDB from inferring the value column + // type as number from the first row, which corrupts subsequent string/boolean values. + await metadataTable.add([{ key, value: String(value) }]) } private async _persistEmbeddingProfile(metadataTable: Table): Promise { await this._upsertMetadata(metadataTable, KEY.provider, this.profile.provider) await this._upsertMetadata(metadataTable, KEY.model, this.profile.modelId) - await this._upsertMetadata(metadataTable, KEY.dimension, this.profile.dimension) - await this._upsertMetadata(metadataTable, KEY.size, this.vectorSize) + await this._upsertMetadata(metadataTable, KEY.dimension, String(this.profile.dimension)) + await this._upsertMetadata(metadataTable, KEY.size, String(this.vectorSize)) } /** @@ -609,7 +611,7 @@ export class LanceDBVectorStore implements IVectorStore { const db = await this.getDb() const metadataTable = await db.openTable(this.metadataTableName) await this._persistEmbeddingProfile(metadataTable) - await this._upsertMetadata(metadataTable, KEY.complete, true) + await this._upsertMetadata(metadataTable, KEY.complete, "true") log.info("Marked indexing as complete") } catch (error) { log.error("Failed to mark indexing as complete", { error }) @@ -626,7 +628,7 @@ export class LanceDBVectorStore implements IVectorStore { const db = await this.getDb() const metadataTable = await db.openTable(this.metadataTableName) await this._persistEmbeddingProfile(metadataTable) - await this._upsertMetadata(metadataTable, KEY.complete, false) + await this._upsertMetadata(metadataTable, KEY.complete, "false") log.info("Marked indexing as incomplete (in progress)") } catch (error) { log.error("Failed to mark indexing as incomplete", { error })