#21053·ai

generateVideo polling timeout does not cancel in-flight status requests

Author: brennanbutler01Created Sep 18, 2026Updated Sep 18, 2026
Labelstask-identify-issue-type-donetask-bug-reproduction-successfactory-activefactory-automatictask-identify-harness-labels-done

Description

experimental_generateVideo checks poll.timeoutMs between polls, but does not apply it to an in-flight doStatus request.

With timeoutMs: 50, a status request that waits for its abort signal is still pending after 200 ms and its signal has not been aborted. If that request instead returns a completed result after the deadline, the SDK accepts it as a successful generation.

Expected: the polling deadline stops waiting and cancels the pending status request. This concerns the local status request, not cancelling the remote video-generation job.

The reference documentation describes timeoutMs as the maximum wait for completion. In executeStartStatusFlow, elapsed time is checked before await retry(() => model.doStatus(...)), but the request only receives the caller's signal. There is no deadline signal or elapsed-time check when the status request completes.

Reproduction

No API key or network calls needed. Install [email protected] and [email protected], save as repro.mts, and run node repro.mts with Node 24:

typescript
import { experimental_generateVideo as generateVideo } from 'ai';
import { MockVideoModelV4 } from 'ai/test';

const caller = new AbortController();
const entered = Promise.withResolvers<void>();
let statusSignal: AbortSignal | undefined;
let state = 'pending';
const model = new MockVideoModelV4({
  doGenerate: undefined,
  doStart: async () => ({
    operation: 'synthetic-operation', warnings: [],
    response: { timestamp: new Date(), modelId: 'synthetic-video', headers: {} },
  }),
  doStatus: ({ abortSignal }) => {
    statusSignal = abortSignal;
    return new Promise((_resolve, reject) => {
      if (abortSignal?.aborted) reject(abortSignal.reason);
      else abortSignal?.addEventListener('abort', () => reject(abortSignal.reason), { once: true });
      entered.resolve();
    });
  },
});
const generation = generateVideo({
  model, prompt: 'Synthetic prompt.', maxRetries: 0,
  abortSignal: caller.signal,
  poll: { intervalMs: 0, timeoutMs: 50 },
}).then(() => { state = 'resolved'; }, () => { state = 'rejected'; });
await entered.promise;
await new Promise(resolve => setTimeout(resolve, 200));
console.log({ state, statusRequestAborted: statusSignal?.aborted });
// Expected after the 50 ms deadline: rejected, true.
// Actual after 200 ms: pending, false.
caller.abort(new Error('Synthetic cleanup'));
await generation;

Actual output after the deadline:

{ state: 'pending', statusRequestAborted: false }

Expected: { state: 'rejected', statusRequestAborted: true }.

The model honors cancellation: the final caller.abort() immediately releases the request. The missing part is applying the polling deadline to that signal.

On Node 22.20.0 and 24.13.0, local regression checks reproduce both the stalled-status and late-success cases. Four controls pass: immediate completion, completion before the deadline, repeated pending polls timing out, and caller cancellation during a status request. The reproduction passes strict TypeScript checking.

The relevant source and documentation are unchanged on main at 12845693d7a6a517dc633d6b6a2e4f5bfd24d2ec (source comparison; runtime tests used the published package). I checked #12515 and the xAI polling reports #17308 / #18048; those do not address the core deadline around doStatus.

I used AI assistance to investigate and prepare the reproduction and tests.

AI SDK Version

  • ai: 7.0.101
  • zod: 4.4.3
  • Node.js: 22.20.0 and 24.13.0

Code of Conduct

  • I agree to follow this project's Code of Conduct