Skip to content

Commit 8947ca5

Browse files
fix(memory): let the deadline result win before aborting the search
Abort listeners dispatch synchronously, so an abort-aware search could reject the raced task before the timeout promise resolved and replace the stable 'memory_search timed out after 15s' result with a provider-wrapped abort error. Resolve the timeout first, then abort.
1 parent 5b66457 commit 8947ca5

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

extensions/memory-core/src/tools.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,35 @@ describe("memory_search unavailable payloads", () => {
188188
}
189189
});
190190

191+
it("keeps the timeout result when an abort-aware search rejects on abort", async () => {
192+
vi.useFakeTimers();
193+
try {
194+
setMemorySearchImpl(
195+
async (opts) =>
196+
await new Promise((_resolve, reject) => {
197+
opts?.signal?.addEventListener(
198+
"abort",
199+
() => reject(new Error("openai-compatible embeddings query failed: aborted")),
200+
{ once: true },
201+
);
202+
}),
203+
);
204+
const tool = createMemorySearchToolOrThrow();
205+
206+
const resultPromise = tool.execute("abort-aware-timeout", { query: "hello" });
207+
await vi.advanceTimersByTimeAsync(15_000);
208+
209+
const result = await resultPromise;
210+
expectUnavailableMemorySearchDetails(result.details, {
211+
error: "memory_search timed out after 15s",
212+
warning: "Memory search is unavailable due to an embedding/provider error.",
213+
action: "Check embedding provider configuration and retry memory_search.",
214+
});
215+
} finally {
216+
vi.useRealTimers();
217+
}
218+
});
219+
191220
it("re-resolves the manager once when a cached sqlite handle was closed", async () => {
192221
let searchCalls = 0;
193222
setMemorySearchImpl(async () => {

extensions/memory-core/src/tools.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,11 @@ async function runMemorySearchToolWithDeadline<T>(params: {
9494
let timer: ReturnType<typeof setTimeout> | undefined;
9595
const timeoutPromise = new Promise<"timeout">((resolve) => {
9696
timer = setTimeout(() => {
97-
controller.abort(timeoutError());
97+
// Resolve before aborting: abort listeners run synchronously and an
98+
// abort-aware search could reject the task first, replacing the stable
99+
// timeout result with a provider-wrapped abort error.
98100
resolve("timeout");
101+
controller.abort(timeoutError());
99102
}, params.timeoutMs);
100103
timer.unref?.();
101104
});

0 commit comments

Comments
 (0)