{"record":{"id":"8630361aa3f97a87","repo":"different-ai/openwork","slug":"microphone-capture-is-unavailable-in-this-runtime","errorCode":null,"errorMessage":"Microphone capture is unavailable in this runtime.","messagePattern":"Microphone capture is unavailable in this runtime\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/app/src/react-app/domains/session/voice/voice-panel.tsx","lineNumber":484,"sourceCode":"        requestRealtimeResponse(channel, false);\n      } else {\n        setRuntimeStatus(voiceRealtime.micMuted ? \"muted\" : \"listening\");\n      }\n      return;\n    }\n    if (type === \"error\") {\n      voiceRealtime.responseInProgress = false;\n      const error = readRecord(event, \"error\");\n      const message = typeof error.message === \"string\" ? error.message : \"Realtime returned an error.\";\n      addEntry(\"system\", message, { error: true });\n      setRuntimeStatus(\"error\", message);\n    }\n  }, [addEntry, props.sessionId, requestRealtimeResponse, setRuntimeStatus]);\n\n  const connectRealtime = useCallback(async (audioInput = true) => {\n    const client = props.client;\n    if (!client) throw new Error(\"OpenWork host connection is not ready.\");\n    if (audioInput && !navigator.mediaDevices?.getUserMedia) throw new Error(\"Microphone capture is unavailable in this runtime.\");\n\n    disconnectRealtime(true);\n    setRuntimeStatus(\"connecting\", \"Minting Realtime session...\");\n    const sessionContext = await loadVoiceSessionContext(props.opencodeBaseUrl, props.openworkToken, props.sessionId);\n    const realtimeSession = await client.createVoiceRealtimeSession({ sessionContext });\n\n    const peer = new RTCPeerConnection();\n    voiceRealtime.peer = peer;\n    if (audioInput) {\n      setRuntimeStatus(\"connecting\", \"Requesting microphone...\");\n      const macPermissionGranted = await requestMacMicrophoneAccess();\n      if (!macPermissionGranted) throw new Error(\"macOS denied microphone access. Enable OpenWork in System Settings > Privacy & Security > Microphone, then restart OpenWork.\");\n      const stream = await navigator.mediaDevices.getUserMedia({\n        audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },\n      });\n      voiceRealtime.stream = stream;\n      setMicDiagnostics(stream);\n      for (const track of stream.getAudioTracks()) {","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/app/src/react-app/domains/session/voice/voice-panel.tsx#L466-L502","documentation":"Before connecting the realtime session the panel checks that the runtime supports microphone capture via navigator.mediaDevices?.getUserMedia. In environments where the Media Devices API is unavailable (non-secure contexts, runtimes without media permissions), enabling audio input cannot work, so it throws.","triggerScenarios":"connectRealtime(audioInput = true) when navigator.mediaDevices is undefined or navigator.mediaDevices.getUserMedia is missing — e.g. the page is not served over HTTPS/localhost, or the embedded webview strips media APIs.","commonSituations":"OpenWork web UI accessed over plain http; running in a webview/iframe without allow=\"microphone\"; Linux/Windows builds or CI runtimes lacking the media stack; user launched the app in a browser that blocks getUserMedia.","solutions":["Ensure the app is served over HTTPS or localhost (getUserMedia requires a secure context)","Use the desktop app runtime where the media bridge is available, or grant the webview microphone permission (iframe allow=\"microphone\")","Connect with audioInput = false to use typed voice commands without a microphone","Check navigator.mediaDevices existence in UI and hide/disable the mic option when absent"],"exampleFix":"// before\nawait connectRealtime(true);\n// after\nif (!navigator.mediaDevices?.getUserMedia) {\n  await connectRealtime(false); // typed voice commands only\n  return;\n}\nawait connectRealtime(true);","handlingStrategy":"type-guard","validationCode":"if (!navigator.mediaDevices?.getUserMedia) {\n  toast.error(\"Microphone capture is not supported here. Use the desktop app or HTTPS.\");\n  return;\n}","typeGuard":"const supportsMicCapture = (): boolean =>\n  typeof navigator !== \"undefined\" && !!navigator.mediaDevices?.getUserMedia;","tryCatchPattern":"try {\n  await connectRealtime(true);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"Microphone capture is unavailable\")) {\n    offerTypedVoiceFallback(); // connectRealtime(false)\n  } else throw e;\n}","preventionTips":["Serve the app over HTTPS or localhost (secure context requirement)","Grant microphone permission to the webview/iframe (allow=\"microphone\")","Detect getUserMedia support at startup and hide mic UI when absent","Offer a typed-command fallback mode"],"tags":["voice","microphone","webrtc","permissions"],"backgroundTag":"getusermedia-unsupported","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}