{"record":{"id":"31887bcc00ef68f3","repo":"different-ai/openwork","slug":"realtime-offer-did-not-include-sdp","errorCode":null,"errorMessage":"Realtime offer did not include SDP.","messagePattern":"Realtime offer did not include SDP\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/app/src/react-app/domains/session/voice/voice-panel.tsx","lineNumber":531,"sourceCode":"    const audio = document.createElement(\"audio\");\n    audio.autoplay = true;\n    audio.style.display = \"none\";\n    document.body.appendChild(audio);\n    voiceRealtime.remoteAudio = audio;\n    peer.ontrack = (event) => {\n      audio.srcObject = event.streams[0] ?? null;\n    };\n\n    const channel = peer.createDataChannel(\"oai-events\");\n    voiceRealtime.channel = channel;\n    channel.addEventListener(\"message\", (event) => void handleRealtimeMessage(String(event.data)));\n    channel.addEventListener(\"close\", () => {\n      if (voiceRealtime.channel === channel) setRuntimeStatus(\"idle\");\n    });\n\n    const offer = await peer.createOffer();\n    await peer.setLocalDescription(offer);\n    if (!offer.sdp) throw new Error(\"Realtime offer did not include SDP.\");\n\n    setRuntimeStatus(\"connecting\", \"Opening voice channel...\");\n    const sdpResponse = await desktopFetch(\"https://api.openai.com/v1/realtime/calls\", {\n      method: \"POST\",\n      headers: { Authorization: `Bearer ${realtimeSession.clientSecret}`, \"Content-Type\": \"application/sdp\" },\n      body: offer.sdp,\n    });\n    if (!sdpResponse.ok) {\n      const detail = await sdpResponse.text().catch(() => \"\");\n      throw new Error(`OpenAI Realtime SDP failed: ${sdpResponse.status} ${detail}`.trim());\n    }\n    await peer.setRemoteDescription({ type: \"answer\", sdp: await sdpResponse.text() });\n    await waitForDataChannelOpen(channel);\n    setRealtimeDiagnostics(\"Realtime data channel is open.\");\n    setRuntimeStatus(\"listening\", audioInput ? undefined : \"Connected. Send a typed voice command.\");\n    addEntry(\"system\", `Realtime connected with ${realtimeSession.model} and ${realtimeSession.tools.length} OpenWork tools.`);\n    recordInspectorEvent(\"voice.connected\", { sessionId: props.sessionId, model: realtimeSession.model });\n  }, [","sourceCodeStart":513,"sourceCodeEnd":549,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/app/src/react-app/domains/session/voice/voice-panel.tsx#L513-L549","documentation":"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.","triggerScenarios":"peer.createOffer() returns an RTCSessionDescription with sdp === null, detected right before posting to https://api.openai.com/v1/realtime/calls.","commonSituations":"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.","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"],"exampleFix":"// before\nconst offer = await peer.createOffer();\nawait peer.setLocalDescription(offer);\nif (!offer.sdp) throw new Error(\"Realtime offer did not include SDP.\");\n// after\nconst offer = await peer.createOffer();\nif (!offer.sdp) throw new Error(\"Realtime offer did not include SDP.\");\nawait peer.setLocalDescription(offer);","handlingStrategy":"validation","validationCode":"const offer = await peer.createOffer();\nif (!offer.sdp) {\n  await connectRealtime(audioInput); // retry with a fresh peer\n  return;\n}","typeGuard":"const hasSdp = (d: RTCSessionDescription | null): d is RTCSessionDescription & { sdp: string } =>\n  d != null && typeof d.sdp === \"string\" && d.sdp.length > 0;","tryCatchPattern":"try {\n  await connectRealtime(audioInput);\n} catch (e) {\n  if (e instanceof Error && e.message === \"Realtime offer did not include SDP.\") {\n    setRuntimeStatus(\"error\", \"WebRTC offer failed — retry.\");\n  } else throw e;\n}","preventionTips":["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"],"tags":["webrtc","sdp","voice"],"backgroundTag":"webrtc-sdp-missing","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}