Skip to content

fix(ext/ai): pass the abort signal to the inference API fetch - #748

Open
breken-ai wants to merge 1 commit into
supabase:mainfrom
breken-ai:fix/ai-session-run-abort-signal
Open

breken-ai wants to merge 1 commit into
supabase:mainfrom
breken-ai:fix/ai-session-run-abort-signal

Conversation

@breken-ai

Copy link
Copy Markdown

What kind of change does this PR introduce?

Bug fix

What is the current behavior?

Supabase.ai.Session.run() builds an abort signal from opts.timeout (60s by default) and opts.signal, and then passes it as a third argument to fetch():

const res = await fetch(
  new URL(path, this.inferenceAPIHost),
  { method: "POST", headers: {...}, body: JSON.stringify({...}) },
  { signal },
);

fetch() only takes (input, init), so the signal never reaches the request. If the inference host (for example Ollama) is slow or hangs before it sends headers, then:

  • run("…", { timeout: 2 }) keeps waiting after 2 seconds.
  • run("…", { signal }) keeps waiting after the caller aborts.
  • The request is only cut off when the worker hits its wall-clock limit.

The signal is only checked later, between body reads in parseJSON, which does nothing while fetch() itself is still pending.

I reproduced this on supabase/edge-runtime:v1.77.1. AI_INFERENCE_API_HOST pointed at a TCP server that accepts the connection and never answers:

{"mode":"timeout","outcome":"still pending after 15s","elapsedMs":15004}
{"mode":"signal","outcome":"still pending after 15s","elapsedMs":15012}

What is the new behavior?

The signal goes into the fetch() init, so both the timeout and the caller's signal abort the request:

{"mode":"timeout","outcome":"rejected: TimeoutError: Signal timed out.","elapsedMs":2004}
{"mode":"signal","outcome":"rejected: Error: caller aborted","elapsedMs":2002}

Normal completions are unchanged. I checked an ollama and an openaicompatible non-stream response against a fake host, and both still resolve.

Tests

This adds test_supabase_ai_inference_api_honors_timeout_and_signal and the test cases crates/base/test_cases/ai-inference-api/{main,abort}. The Rust test starts a listener that accepts and never answers, and passes its address to the user worker as AI_INFERENCE_API_HOST. The worker then checks that timeout: 1 rejects with TimeoutError and that an aborted signal rejects with its reason.

I didn't do a full local build of the runtime. Instead, I ran these exact test case files through the released binary:

binary result
v1.77.1 as released {"timeout":"still pending","signal":"still pending"}, HTTP 500, after about 20s
the same binary with this one-line change patched into its embedded ai.js source {"timeout":"TimeoutError: Signal timed out.","signal":"Error: caller aborted"}, HTTP 200, after 1.7s

The changed files pass dprint check. CI will run the Rust test itself.

Additional context

The third-argument call has been there since 383dd52 (2025-01-09).

This PR was prepared with AI assistance (Claude). I reproduced the bug and checked the fix as described above.

Session.run() passed `{ signal }` as a third argument to fetch(), which
fetch ignores. The `timeout` option and a caller's `signal` never reached
the request, so a slow or hung inference host left run() pending until
the worker was killed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

1 participant