{"record":{"id":"9a74daa95d38ebc0","repo":"moeru-ai/airi","slug":"1-microphone-permission-not-granted-browser-sho-9a74da","errorCode":null,"errorMessage":"1. Microphone permission not granted - browser should prompt automatically","messagePattern":"1\\. Microphone permission not granted - browser should prompt automatically","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/provider-inference/src/providers/local/browser-web-speech-api/provider.ts","lineNumber":458,"sourceCode":"\n  recognition.onsoundend = () => {\n    console.info('Web Speech API sound ended')\n  }\n\n  recognition.onaudioend = () => {\n    console.info('Web Speech API audio capture ended')\n  }\n\n  recognition.onnomatch = () => {\n    console.info('Web Speech API: No speech match')\n  }\n\n  const started = startRecognition()\n  if (!started) {\n    // If immediate start failed, it might be a permission issue\n    // Web Speech API will prompt for permission automatically, so we just log\n    console.warn('Web Speech API recognition did not start immediately. This might be due to:')\n    console.warn('1. Microphone permission not granted - browser should prompt automatically')\n    console.warn('2. Recognition already running - this is normal if called multiple times')\n    console.warn('3. Browser requires user gesture - ensure microphone was enabled by user action')\n\n    // Don't retry immediately - wait for permission or user action\n    // The recognition instance is already created, so it can be started later if needed\n  }\n\n  return {\n    fullStream,\n    text: deferredText.promise,\n    textStream,\n    recognition: recognitionInstance,\n  }\n}\n","sourceCodeStart":440,"sourceCodeEnd":473,"githubUrl":"https://github.com/moeru-ai/airi/blob/f679616c34f1cf6d282c8d64264242af944b3fed/packages/provider-inference/src/providers/local/browser-web-speech-api/provider.ts#L440-L473","documentation":"This is line 2 of the same multi-line diagnostic `console.warn` block in `streamWebSpeechAPITranscription`, emitted when the Web Speech recognition did not start immediately. This line specifically names missing microphone permission as the most likely cause, noting that the browser should prompt the user automatically. It is informational output, not a thrown error; a hard permission failure is separately surfaced as 'Microphone permission denied.'","triggerScenarios":"Same as the parent warning: `startRecognition()` returned false because `recognition.start()` failed, with ungranted microphone permission being the leading suspect — e.g. the user has not yet answered the mic permission prompt or previously denied it.","commonSituations":"First-time use of voice input before granting mic access; permission previously blocked in browser site settings; embedded WebView (Electron/Capacitor) without mic permission pre-granted; calling before any user gesture so the prompt cannot appear.","solutions":["Ensure the call originates from a user gesture so the browser can show the mic permission prompt.","Check `navigator.permissions.query({ name: 'microphone' })`; if 'denied', instruct the user to re-enable the mic in site settings.","In Electron/Capacitor, pre-grant or request microphone permission through the platform API before starting transcription.","Listen for the recognizer's 'not-allowed' error, which the provider converts into a hard 'Microphone permission denied.' stream error."],"exampleFix":"// before\nstartListening() // page load, no gesture — prompt never shows\n\n// after\nmicButton.onclick = async () => {\n  await navigator.mediaDevices.getUserMedia({ audio: true }) // triggers prompt inside gesture\n  startListening()\n}","handlingStrategy":"validation","validationCode":"const perm = await navigator.permissions.query({ name: 'microphone' })\nif (perm.state !== 'granted') {\n  // must be called from a user gesture to surface the prompt\n  const stream = await navigator.mediaDevices.getUserMedia({ audio: true })\n  stream.getTracks().forEach(t => t.stop())\n}","typeGuard":"function hasMicPermission(state: PermissionState): state is 'granted' { return state === 'granted' }","tryCatchPattern":"try {\n  const stream = await navigator.mediaDevices.getUserMedia({ audio: true })\n  stream.getTracks().forEach(t => t.stop())\n  await startTranscription()\n} catch (error) {\n  if (error instanceof DOMException && error.name === 'NotAllowedError') {\n    showMicPermissionInstructions()\n  }\n}","preventionTips":["Request mic permission explicitly via getUserMedia inside a click handler before using Web Speech.","Detect 'denied' permission state early and show instructions to unblock the mic in site settings.","On Electron/Capacitor, handle platform-level mic permission requests before voice features.","Handle the recognizer's 'not-allowed' error event as the authoritative permission signal."],"tags":["web-speech-api","microphone","permission","browser"],"backgroundTag":"permission-denied","analyzedSha":"f679616c34f1cf6d282c8d64264242af944b3fed","analyzedAt":"2026-09-08T13:18:15.005Z","contentChangedAt":"2026-09-08T13:18:15.005Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}