From 45e12e5bb758198a920c6070e7e64cb33b21beac Mon Sep 17 00:00:00 2001 From: can1357 Date: Sun, 9 Aug 2026 03:39:03 +0200 Subject: [PATCH] test(mnemopi): awaited detached shared-bank flush in lock-wait test dispose({ timeoutMs }) legitimately detaches the consolidate pass when the shutdown budget expires mid-flight (#3641), so asserting the shared bank flush synchronously after dispose raced the detached pass on slow CI runners (0 calls observed on run 31287979000). Signal the flush call through a deferred and await it after releasing the lock; the bounded <500ms return assertion is unchanged. --- .../coding-agent/test/memory-tools.test.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/coding-agent/test/memory-tools.test.ts b/packages/coding-agent/test/memory-tools.test.ts index 37e7b5505..c0d409ad8 100644 --- a/packages/coding-agent/test/memory-tools.test.ts +++ b/packages/coding-agent/test/memory-tools.test.ts @@ -743,7 +743,11 @@ describe("Mnemopi backend lifecycle", () => { lock.exec("BEGIN IMMEDIATE"); const sharedMemory = state.globalMemory; expect(sharedMemory).toBeDefined(); + const sharedFlushCalled = Promise.withResolvers(); const sharedFlushSpy = vi.spyOn(sharedMemory!, "flushExtractions").mockImplementation(async () => { + // Signal first: the exec below may throw SQLITE_BUSY while the lock is + // still held, and the call itself is what the test awaits. + sharedFlushCalled.resolve(); // Model a pending extraction/embedding commit. An idle shared bank performs // no SQLite work during flush, so merely locking it would not exercise its // connection's busy timeout. @@ -756,11 +760,21 @@ describe("Mnemopi backend lifecycle", () => { } finally { lock.exec("ROLLBACK"); lock.close(); + } + const elapsedMs = performance.now() - started; + + try { + expect(elapsedMs).toBeLessThan(500); + // When the shutdown budget expires mid-consolidate, dispose detaches the + // pass instead of abandoning it (#3641) — so on a slow runner the shared + // flush may not have run yet when dispose returns. The lock is released + // above, so the detached pass must still reach the shared bank; await + // the call itself instead of asserting synchronously. + await sharedFlushCalled.promise; + expect(sharedFlushSpy).toHaveBeenCalledTimes(1); + } finally { registeredMnemopiState = undefined; } - - expect(performance.now() - started).toBeLessThan(500); - expect(sharedFlushSpy).toHaveBeenCalledTimes(1); }); it("dispose with no timeoutMs retains, flushes, and closes without sleeping (#3641)", async () => {