Skip to content

fix(tools): keep the forget-memory timeout when a caller passes a signal - #1595

Open
Agnik47 wants to merge 1 commit into
supermemoryai:mainfrom
Agnik47:fix/forget-memory-signal-timeout
Open

fix(tools): keep the forget-memory timeout when a caller passes a signal#1595
Agnik47 wants to merge 1 commit into
supermemoryai:mainfrom
Agnik47:fix/forget-memory-signal-timeout

Conversation

@Agnik47

@Agnik47 Agnik47 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #1549.

The problem

packages/tools/src/shared/forget-memory.ts combined the caller's signal and the 30s abort with ??, which makes them mutually exclusive:

signal: options?.signal ?? AbortSignal.timeout(FETCH_TIMEOUT_MS),

Pass a cancellation signal and the request becomes unbounded again — exactly the condition #1451 set out to remove. A caller who wants both cancellation and a timeout had no way to express it.

Latent rather than live today: neither production call site (ai-sdk.ts, openai/tools.ts) passes options. It becomes a real hang the first time someone wires up cancellation.

The fix

Compose the two signals instead of choosing between them:

signal: options?.signal
    ? AbortSignal.any([options.signal, AbortSignal.timeout(FETCH_TIMEOUT_MS)])
    : AbortSignal.timeout(FETCH_TIMEOUT_MS),

AbortSignal.any is available in Node 20.3+, Bun and workerd. Root package.json declares "node": ">=20", so in theory 20.0–20.2 are below the floor for this API; every maintained 20.x release is well past 20.3. Happy to guard it if you'd rather not rely on that.

Tests

The existing case — "uses a caller-provided signal instead of creating a timeout"asserted the buggy behaviour (expect(init.signal).toBe(controller.signal)), so it could not survive the fix. It is replaced by two tests that pin the composed semantics:

  • cancels through a caller-provided signal — the request signal is a composite rather than the caller's own, and aborting the caller still aborts the request.
  • keeps the timeout when a caller-provided signal is presentAbortSignal.timeout is still called with 30_000, and firing only the timeout leg aborts the request.

Both new tests fail against the previous implementation (verified by reverting the source and keeping the tests):

× memoryForget > cancels through a caller-provided signal
  → expected AbortSignal { aborted: false } not to be AbortSignal { aborted: false }
× memoryForget > keeps the timeout when a caller-provided signal is present
  → expected "timeout" to be called with arguments: [ 30000 ]

Verification

Unrelated, but noticed nearby

apps/mcp/src/server/client/index.ts:339 (getDocuments) has the identical options?.signal ?? AbortSignal.timeout(...) shape. Out of scope here — say the word and I'll open a separate issue or PR for it.

`forgetMemoryRequest` combined the caller's signal and the 30s abort with
`??`, making them mutually exclusive. Passing a cancellation signal removed
the timeout, so a hung `DELETE /v4/memories` could wedge the tool call again
— the exact condition supermemoryai#1451 set out to remove. There was also no way for a
caller to ask for both cancellation and a timeout.

Compose the two with `AbortSignal.any` instead of choosing between them.
`AbortSignal.any` is available in Node 20.3+, Bun and workerd.

No production call site passes `options` today (`ai-sdk.ts` and
`openai/tools.ts` both omit it), so this was latent rather than live.

The existing test asserted the buggy behaviour (`init.signal` being the
caller's own signal), so it is replaced by two tests that pin the composed
semantics: aborting the caller aborts the request, and the timeout leg still
aborts the request on its own. Both fail against the previous implementation.

Fixes supermemoryai#1549
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

forgetMemoryRequest drops its 30s timeout whenever a caller passes a signal

1 participant