{"record":{"id":"2f56504c3df6b55a","repo":"different-ai/openwork","slug":"openai-realtime-sdp-failed-sdpresponse-status","errorCode":null,"errorMessage":"OpenAI Realtime SDP failed: ${sdpResponse.status} ${detail}","messagePattern":"OpenAI Realtime SDP failed: (.+?) (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/app/src/react-app/domains/session/voice/voice-panel.tsx","lineNumber":541,"sourceCode":"    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  }, [\n    addEntry,\n    disconnectRealtime,\n    handleRealtimeMessage,\n    props.client,\n    props.opencodeBaseUrl,\n    props.openworkToken,\n    props.sessionId,\n    setRuntimeStatus,\n  ]);\n","sourceCodeStart":523,"sourceCodeEnd":559,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/app/src/react-app/domains/session/voice/voice-panel.tsx#L523-L559","documentation":"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.","triggerScenarios":"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).","commonSituations":"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).","solutions":["Re-mint the voice session (reconnect) to get a fresh clientSecret and retry","Read the `detail` in the message — 401 => session/secret expired, 400 => inspect SDP/params, 429/5xx => back off and retry later","Verify network/proxy settings that desktopFetch uses can reach api.openai.com","Check OpenAI status page if failures are persistent across fresh sessions"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const sessionContext = await loadVoiceSessionContext(baseUrl, token, sessionId); // mint fresh, don't reuse\nif (Date.parse(sessionContext.expiresAt) < Date.now() + 30_000) {\n  sessionContext = await loadVoiceSessionContext(baseUrl, token, sessionId);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await connectRealtime(audioInput);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith(\"OpenAI Realtime SDP failed:\")) {\n    const status = Number(e.message.match(/SDP failed: (\\d+)/)?.[1]);\n    if (status === 401 || status === 429 || status >= 500) await reconnectWithFreshSession();\n    else toast.error(e.message);\n  } else throw e;\n}","preventionTips":["Always mint a fresh realtime session immediately before connecting (clientSecrets are short-lived)","Retry with backoff on 429/5xx; re-auth on 401","Check OpenAI status before mass rollouts of realtime features","Log the response `detail` to distinguish malformed-SDP (400) from auth/expiry issues"],"tags":["webrtc","sdp","openai","network"],"backgroundTag":"sdp-negotiation-failed","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}