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
- Retry connectRealtime — a fresh createOffer usually yields valid SDP
- Check that the RTCPeerConnection is created before any tracks close and the peer is not closed when creating the offer
- Verify the embedded runtime's WebRTC support; use a standard Chromium/Electron runtime
- 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
- Create the offer on an open RTCPeerConnection with transceivers added first
- Verify the runtime's WebRTC implementation (standard Electron/Chromium)
- Log offer.sdp length to catch regressions early
- Retry the connection once automatically on empty SDP
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
- OpenWork host connection is not ready.
- Microphone capture is unavailable in this runtime.
- OpenAI Realtime SDP failed: ${sdpResponse.status} ${detail}
- macOS denied microphone access. Enable OpenWork in System Se
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/31887bcc00ef68f3.
Report an issue: GitHub.