From a2e5038a29884bc606e45d59d5de2788d0d55b67 Mon Sep 17 00:00:00 2001 From: Developer Date: Thu, 9 Apr 2026 00:04:51 +0800 Subject: [PATCH 1/2] test: improve retry logic for memory_search flaky test on Node 22 - Add initial 200ms delay after capture before first search attempt - Increase retry count from 3 to 5 - Increase delay between retries from 100ms to 150ms - Simplify retryWithDelay to return last result instead of throwing --- test/regression/plugin.test.ts | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/test/regression/plugin.test.ts b/test/regression/plugin.test.ts index 4f7d37c..49e17d4 100644 --- a/test/regression/plugin.test.ts +++ b/test/regression/plugin.test.ts @@ -247,23 +247,18 @@ async function retryWithDelay( delayMs = 100, shouldRetry?: (result: T) => boolean, ): Promise { - let lastError: unknown; + let lastResult: T | undefined; for (let attempt = 0; attempt < maxAttempts; attempt++) { if (attempt > 0) { await new Promise((resolve) => setTimeout(resolve, delayMs * attempt)); } - try { - const result = await fn(); - if (!shouldRetry || !shouldRetry(result)) { - return result; - } - lastError = new Error(`Attempt ${attempt + 1} returned result that needs retry`); - } catch (e) { - lastError = e; - if (attempt === maxAttempts - 1) throw e; + const result = await fn(); + lastResult = result; + if (!shouldRetry || !shouldRetry(result)) { + return result; } } - throw lastError; + return lastResult as T; } test("auto-capture stores qualifying output with decision category and skips short output", async () => { @@ -463,14 +458,14 @@ test("memory_delete and memory_clear reject destructive operations without confi try { await harness.capture("Resolved successfully after rotating the stale token and reloading the API gateway config."); - // Use retryWithDelay to handle async write delay - search might not find immediately after capture + await new Promise((resolve) => setTimeout(resolve, 200)); const searchOutput = await retryWithDelay( () => withPatchedFetch(() => harness.toolHooks.memory_search.execute({ query: "stale token API gateway", limit: 5 }, harness.context), ), - 3, - 100, + 5, + 150, (result) => result === "No relevant memory found." || !result.match(/\[([^\]]+)\]/), ); const recordId = searchOutput.match(/\[([^\]]+)\]/)?.[1]; From 971a066b715c5c1829023fd53556e61dad3dc0d4 Mon Sep 17 00:00:00 2001 From: Developer Date: Thu, 9 Apr 2026 00:15:55 +0800 Subject: [PATCH 2/2] fix: await updateMemoryUsage to prevent read-write race on Node 22 updateMemoryUsage implements delete+add internally. When fire-and-forget, a subsequent memory_search can land in the delete/add gap and observe zero rows, returning 'No relevant memory found.' in Node 22. Fix: await the update in memory_search. Test: remove unnecessary retry/delay workaround now that root cause is fixed. --- src/tools/memory.ts | 5 ++++- test/regression/plugin.test.ts | 11 ++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/tools/memory.ts b/src/tools/memory.ts index a93ffb4..ca0c527 100644 --- a/src/tools/memory.ts +++ b/src/tools/memory.ts @@ -118,7 +118,10 @@ export function createMemoryTools(state: ToolRuntimeState) { if (results.length === 0) return "No relevant memory found."; for (const result of results) { - state.store.updateMemoryUsage(result.record.id, activeScope, scopes).catch(() => {}); + try { + await state.store.updateMemoryUsage(result.record.id, activeScope, scopes); + } catch { + } } return results diff --git a/test/regression/plugin.test.ts b/test/regression/plugin.test.ts index 49e17d4..d7a02ce 100644 --- a/test/regression/plugin.test.ts +++ b/test/regression/plugin.test.ts @@ -458,15 +458,8 @@ test("memory_delete and memory_clear reject destructive operations without confi try { await harness.capture("Resolved successfully after rotating the stale token and reloading the API gateway config."); - await new Promise((resolve) => setTimeout(resolve, 200)); - const searchOutput = await retryWithDelay( - () => - withPatchedFetch(() => - harness.toolHooks.memory_search.execute({ query: "stale token API gateway", limit: 5 }, harness.context), - ), - 5, - 150, - (result) => result === "No relevant memory found." || !result.match(/\[([^\]]+)\]/), + const searchOutput = await withPatchedFetch(() => + harness.toolHooks.memory_search.execute({ query: "stale token API gateway", limit: 5 }, harness.context), ); const recordId = searchOutput.match(/\[([^\]]+)\]/)?.[1]; assert.ok(recordId, `Expected recordId in searchOutput, got: ${searchOutput}`);