Skip to content

fix(run): honour AbortSignal at start and after polling#380

Open
tsushanth wants to merge 1 commit into
replicate:mainfrom
tsushanth:fix/run-aborts-signal
Open

fix(run): honour AbortSignal at start and after polling#380
tsushanth wants to merge 1 commit into
replicate:mainfrom
tsushanth:fix/run-aborts-signal

Conversation

@tsushanth

Copy link
Copy Markdown

Closes #370.

replicate.run() accepted options.signal but only checked it as a poll-exit condition. Two paths still silently completed:

  1. A signal that was already aborted before run() was called fell through to predictions.create() and waited on the prediction as usual.
  2. A signal that aborted during polling broke the poll loop, but the caller's awaited promise resolved with the empty output of the canceled prediction (or undefined) instead of throwing — the cancellation was invisible to the consumer.

Fix

  • Call signal.throwIfAborted() before any HTTP work happens, matching the fetch / standard AbortController contract.
  • Forward signal to predictions.create() so the initial create fetch is itself abortable.
  • After the poll loop exits with an aborted signal, best-effort cancel the prediction (swallowing any cancel-side error so the abort remains the higher-priority signal) and then call signal.throwIfAborted() so the awaited promise rejects with AbortError.

Tests

The existing Aborts the operation when abort signal is invoked test asserted output was undefined after abort — that's the bug-as-spec, so it's updated to assert an AbortError is thrown instead. Two new regression cases cover:

  • A signal that is already aborted before run() short-circuits before any HTTP request fires (verified by not registering any nock scope — an unintended request would surface as Nock: No match).
  • The progress callback is still called for the starting and processing updates before the throw.

All 81 tests in index.test.ts pass.

Closes replicate#370.

`replicate.run()` accepted `options.signal` but only checked it as a poll
exit condition. Two paths still silently completed:

1. A signal that was already aborted before `run()` was called fell through
   to `predictions.create()` and waited on the prediction as usual.
2. A signal that aborted during polling broke the poll loop, but the
   caller's awaited promise resolved with the empty output of the canceled
   prediction (or `undefined`) instead of throwing — the cancellation was
   invisible to the consumer.

`run()` now:

- Calls `signal.throwIfAborted()` before any HTTP work happens, matching
  the `fetch`/standard-AbortController contract.
- Forwards `signal` to `predictions.create()` so the initial create fetch
  is itself abortable.
- After the poll loop exits with an aborted signal, best-effort cancels
  the prediction (swallowing any cancel-side error so the abort remains
  the higher-priority signal) and then calls `signal.throwIfAborted()` so
  the awaited promise rejects with `AbortError`.

The existing "Aborts the operation when abort signal is invoked" test is
updated to assert the new throwing contract; two new regression cases
cover the pre-aborted-signal short-circuit and the no-network guarantee.

Co-Authored-By: Claude Opus 4.7 (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.

replicate.run() ignores AbortSignal - doesn't throw when signal is already aborted or becomes aborted during polling

1 participant