different-ai/openwork · error

Realtime offer did not include SDP.

Error message

Realtime offer did not include SDP.

What it means

After peer.createOffer() and setLocalDescription, the code asserts the offer contains SDP text. The WebRTC spec allows an offer whose sdp field is null (e.g. offerToReceiveAudio-style edge cases or a mangled local description), and the SDP POST to OpenAI requires a body — so a null SDP aborts the connection.

Source

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

    const audio = document.createElement("audio");
    audio.autoplay = true;
    audio.style.display = "none";
    document.body.appendChild(audio);
    voiceRealtime.remoteAudio = audio;
    peer.ontrack = (event) => {
      audio.srcObject = event.streams[0] ?? null;
    };

    const channel = peer.createDataChannel("oai-events");
    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 });
  }, [

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Retry connectRealtime — a fresh createOffer usually yields valid SDP
  2. Check that the RTCPeerConnection is created before any tracks close and the peer is not closed when creating the offer
  3. Verify the embedded runtime's WebRTC support; use a standard Chromium/Electron runtime
  4. Log offer.sdp length on failure and report the runtime version if reproducible

Example fix

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

Strategy: validation

Validate before calling

const offer = await peer.createOffer();
if (!offer.sdp) {
  await connectRealtime(audioInput); // retry with a fresh peer
  return;
}

Type guard

const hasSdp = (d: RTCSessionDescription | null): d is RTCSessionDescription & { sdp: string } =>
  d != null && typeof d.sdp === "string" && d.sdp.length > 0;

Try / catch

try {
  await connectRealtime(audioInput);
} catch (e) {
  if (e instanceof Error && e.message === "Realtime offer did not include SDP.") {
    setRuntimeStatus("error", "WebRTC offer failed — retry.");
  } else throw e;
}

Prevention

When it happens

Trigger: peer.createOffer() returns an RTCSessionDescription with sdp === null, detected right before posting to https://api.openai.com/v1/realtime/calls.

Common situations: Running in a webview/runtime with a broken or non-standard WebRTC implementation; peer connection created with no audio/video transceivers in an unusual order; browser/RTCEngine bug or version regression; offer created after the peer already closed.

Related errors


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