From ddbfcf79820c8fc03ceb6dee81239318587b2246 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Wed, 2 Sep 2026 03:34:28 -0400 Subject: [PATCH] fix(ai-vault-search): clear a file's stale mark once the backfill indexes it List scans queue every not-yet-indexed file as stale while the backfill runs; nothing cleared the mark when the backfill reached the file, so the first search after a backfill re-parsed the whole queue (8.4 s on 4,000 sessions). --- .../ai-vault-search/session-search-store.test.ts | 15 +++++++++++++++ src/main/ai-vault-search/session-search-store.ts | 3 +++ 2 files changed, 18 insertions(+) diff --git a/src/main/ai-vault-search/session-search-store.test.ts b/src/main/ai-vault-search/session-search-store.test.ts index 10b4757ae6a..123252da4c7 100644 --- a/src/main/ai-vault-search/session-search-store.test.ts +++ b/src/main/ai-vault-search/session-search-store.test.ts @@ -149,6 +149,21 @@ describe('SessionSearchStore', () => { expect(store.coverage().filesPending).toBe(0) }) + it('drops a stale mark once the backfill indexes the file', async () => { + const root = await makeTempDir() + const path = join(root, `${SESSION_ID}.jsonl`) + await writeFile(path, `${userRecord(0, 'queued behind the backfill')}\n`) + registerSessionSearchIndexSink(null) + await parse(path) + registerSessionSearchIndexSink(store) + await parse(path) + expect(store.coverage().filesPending).toBe(1) + + await withSessionSearchIndexRequired(() => parse(path)) + expect(store.coverage()).toMatchObject({ sessionsIndexed: 1, filesPending: 0 }) + expect(store.takeStale()).toEqual([]) + }) + it('repairs a typo from the index vocabulary', async () => { const root = await makeTempDir() const path = join(root, `${SESSION_ID}.jsonl`) diff --git a/src/main/ai-vault-search/session-search-store.ts b/src/main/ai-vault-search/session-search-store.ts index b8bf6a66ff8..57cd76aa779 100644 --- a/src/main/ai-vault-search/session-search-store.ts +++ b/src/main/ai-vault-search/session-search-store.ts @@ -58,6 +58,9 @@ export class SessionSearchStore implements SessionSearchIndexSink { apply(update: SessionSearchIndexUpdate): void { try { this.writer.apply(update) + // Why: list scans queue every file the backfill has not reached yet; once + // it lands, a later search must not re-parse the whole queue (8 s live). + this.stale.delete(update.candidate.file.path) this.lastIndexedAt = new Date().toISOString() } catch (error) { this.applyFailures += 1