different-ai/openwork · error

OpenAI Realtime SDP failed: ${sdpResponse.status} ${detail}

Error message

OpenAI Realtime SDP failed: ${sdpResponse.status} ${detail}

What it means

The WebRTC offer SDP is POSTed to OpenAI's Realtime calls endpoint with the session's clientSecret. If the HTTP response is not ok, the panel reads the error body (best-effort) and throws with the status code and detail, so WebRTC negotiation with OpenAI failed at the SDP exchange step.

Source

Thrown at apps/app/src/react-app/domains/session/voice/voice-panel.tsx:541

    voiceRealtime.channel = channel;
    channel.addEventListener("message", (event) => void handleRealtimeMessage(String(event.data)));
    channel.addEventListener("close", () => {
      if (voiceRealtime.channel === channel) setRuntimeStatus("idle");
    });

    const offer = await peer.createOffer();
    await peer.setLocalDescription(offer);
    if (!offer.sdp) throw new Error("Realtime offer did not include SDP.");

    setRuntimeStatus("connecting", "Opening voice channel...");
    const sdpResponse = await desktopFetch("https://api.openai.com/v1/realtime/calls", {
      method: "POST",
      headers: { Authorization: `Bearer ${realtimeSession.clientSecret}`, "Content-Type": "application/sdp" },
      body: offer.sdp,
    });
    if (!sdpResponse.ok) {
      const detail = await sdpResponse.text().catch(() => "");
      throw new Error(`OpenAI Realtime SDP failed: ${sdpResponse.status} ${detail}`.trim());
    }
    await peer.setRemoteDescription({ type: "answer", sdp: await sdpResponse.text() });
    await waitForDataChannelOpen(channel);
    setRealtimeDiagnostics("Realtime data channel is open.");
    setRuntimeStatus("listening", audioInput ? undefined : "Connected. Send a typed voice command.");
    addEntry("system", `Realtime connected with ${realtimeSession.model} and ${realtimeSession.tools.length} OpenWork tools.`);
    recordInspectorEvent("voice.connected", { sessionId: props.sessionId, model: realtimeSession.model });
  }, [
    addEntry,
    disconnectRealtime,
    handleRealtimeMessage,
    props.client,
    props.opencodeBaseUrl,
    props.openworkToken,
    props.sessionId,
    setRuntimeStatus,
  ]);

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Re-mint the voice session (reconnect) to get a fresh clientSecret and retry
  2. Read the `detail` in the message — 401 => session/secret expired, 400 => inspect SDP/params, 429/5xx => back off and retry later
  3. Verify network/proxy settings that desktopFetch uses can reach api.openai.com
  4. Check OpenAI status page if failures are persistent across fresh sessions
Defensive patterns

Strategy: retry

Validate before calling

const sessionContext = await loadVoiceSessionContext(baseUrl, token, sessionId); // mint fresh, don't reuse
if (Date.parse(sessionContext.expiresAt) < Date.now() + 30_000) {
  sessionContext = await loadVoiceSessionContext(baseUrl, token, sessionId);
}

Try / catch

try {
  await connectRealtime(audioInput);
} catch (e) {
  if (e instanceof Error && e.message.startsWith("OpenAI Realtime SDP failed:")) {
    const status = Number(e.message.match(/SDP failed: (\d+)/)?.[1]);
    if (status === 401 || status === 429 || status >= 500) await reconnectWithFreshSession();
    else toast.error(e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: desktopFetch to https://api.openai.com/v1/realtime/calls returns a non-2xx status — e.g. 401 from an expired/invalid clientSecret, 400 from malformed SDP, 429/5xx from OpenAI side; detail is the response body text (empty on read failure).

Common situations: Realtime session context minted too long ago so clientSecret expired; OpenAI outage or region block; network/proxy/TLS issue in desktopFetch; model or voice params in sessionContext rejected by the API (400).

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/2f56504c3df6b55a. Report an issue: GitHub.