mastra-ai/mastra · error · Error

Cannot start an overlapping WebSocket Responses continuation

Error message

Cannot start an overlapping WebSocket Responses continuation. Wait for the active stream to finish before sending previous_response_id.

What it means

The OpenAI WebSocket Responses transport maintains a connection-local previous_response_id cache and supports only one active stream at a time. If a new request arrives while a stream is busy AND it carries a previous_response_id (a continuation depending on socket-local state), the library throws this Error instead of silently corrupting the conversation. Requests without previous_response_id are safely fallen back to HTTP fetch.

Source

Thrown at packages/core/src/llm/model/openai-websocket-fetch.ts:181

    }

    let body: Record<string, unknown>;
    try {
      body = JSON.parse(typeof init.body === 'string' ? init.body : '');
    } catch {
      return globalThis.fetch(input, init);
    }

    if (!body.stream) {
      return globalThis.fetch(input, init);
    }

    // Prevent concurrent streams from sharing one WebSocket transport instance.
    // Only fall back to HTTP when the request does not depend on the socket's
    // connection-local previous_response_id cache.
    if (busy) {
      if (body.previous_response_id) {
        throw new Error(
          'Cannot start an overlapping WebSocket Responses continuation. Wait for the active stream to finish before sending previous_response_id.',
        );
      }
      return globalThis.fetch(input, init);
    }

    const headers = normalizeHeaders(init.headers);
    const authorization =
      headers['authorization'] ?? (options?.apiKeyAsBearer && headers['api-key'] ? `Bearer ${headers['api-key']}` : '');

    // Acquire the busy lock before awaiting to prevent races
    busy = true;
    let connection: WebSocket;
    try {
      connection = await getConnection(authorization, headers, init?.signal);
    } catch (err) {
      busy = false;
      throw err;

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Await the active stream's completion before sending a request with previous_response_id
  2. Serialize requests through a queue/mutex per WebSocket transport instance
  3. Use separate transport instances for concurrent conversations
  4. Drop previous_response_id if the continuation doesn't require socket-local state (it will fall back to HTTP)

Example fix

// before
streamOne.start(); // not awaited
conn.websocketFetch({ previous_response_id: 'resp_1', ... }); // throws
// after
await streamOne.finished;
await conn.websocketFetch({ previous_response_id: 'resp_1', ... });
Defensive patterns

Strategy: fallback

Validate before calling

// Guard before sending a continuation
if (conn.isBusy && body.previous_response_id) {
  throw new Error('Active stream in progress; defer the continuation');
}

Try / catch

let res;
try {
  res = await conn.websocketFetch({ ...body, previous_response_id: lastId });
} catch (e) {
  if (e.message.includes('overlapping WebSocket Responses continuation')) {
    await activeStream.finished; // wait, then retry the continuation
    res = await conn.websocketFetch({ ...body, previous_response_id: lastId });
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling websocketFetch for a Responses continuation (body.previous_response_id set) while another stream is still active on the same WebSocket transport instance.

Common situations: Sending a follow-up message before the previous streamed response finished; concurrent agent turns sharing one client; fire-and-forget stream calls not awaited before issuing the next turn.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/a579da59f5bca74e. Report an issue: GitHub.