vercel/ai · info · DOMException

AbortError

AbortError

Error message

Polling was aborted

What it means

pollGoogleInteractionUntilTerminal polls GET /interactions/{id} until a background Google interaction reaches a terminal status. Before each tick it checks the caller-supplied AbortSignal; if it is already aborted it first fires a best-effort POST /interactions/{id}/cancel so the run stops billing server-side, then throws this AbortError. It is the library's way of honoring cancellation of a long-running background interaction poll.

Source

Thrown at packages/google/src/interactions/poll-google-interactions.ts:91

  const startedAt = Date.now();
  let nextDelayMs = initialDelayMs;
  const url = `${baseURL}/interactions/${encodeURIComponent(interactionId)}`;

  /*
   * When the caller aborts, fire a best-effort `POST /interactions/{id}/cancel`
   * so the run stops billing on Google's side. Wrap every exit path that's
   * triggered by an abort -- the explicit `abortSignal.aborted` check, the
   * AbortError thrown by `delay()`, and any AbortError thrown by `getFromApi`.
   */
  const cancelOnServer = () =>
    cancelGoogleInteraction({ baseURL, interactionId, headers, fetch });

  try {
    while (true) {
      if (abortSignal?.aborted) {
        await cancelOnServer();
        throw new DOMException('Polling was aborted', 'AbortError');
      }

      if (Date.now() - startedAt > timeoutMs) {
        throw new Error(
          `google.interactions: timed out polling interaction ${interactionId} after ${timeoutMs}ms.`,
        );
      }

      await delay(nextDelayMs, { abortSignal });

      const {
        value: response,
        rawValue: rawResponse,
        responseHeaders,
      } = await getFromApi({
        url,
        validateUrl: false,
        headers,

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Catch AbortError (or use error.name === 'AbortError') and treat the interaction as cancelled; the library already sent a server-side cancel.
  2. Increase or restructure the abortSignal lifetime (e.g. use AbortSignal.any with a longer timeout) if cancellation was unintentional.
  3. Pass providerOptions.google.pollingTimeoutMs to raise pollingTimeoutMs so the poll is not cut short before finishing.
  4. Re-poll later by calling the interactions API with the same interactionId if you still need the result.

Example fix

// before
const result = await generateText({ model, ... });
// after
try {
  const result = await generateText({ model, abortSignal: controller.signal, ... });
} catch (error) {
  if (error instanceof Error && error.name === 'AbortError') {
    // interaction was cancelled server-side; handle gracefully
  } else {
    throw error;
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (abortSignal?.aborted) { /* skip the call entirely */ }

Try / catch

try {
  await pollOrGenerate({ abortSignal });
} catch (error) {
  if (error instanceof DOMException && error.name === 'AbortError') {
    // interaction was cancelled server-side; return a graceful response
  } else { throw error; }
}

Prevention

When it happens

Trigger: Calling generateText/streamText (or the interactions API directly) against a background Google interaction while passing an abortSignal, and that signal fires (or is already aborted) while pollGoogleInteractionUntilTerminal is still waiting for a terminal status.

Common situations: User cancels a request in a server handler (req.signal / AbortController.timeout), a client disconnects mid-run, or a timeout AbortSignal fires because a deep-research/agent interaction takes many minutes.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/e1bd9a61f82e236a. Report an issue: GitHub.