{"record":{"id":"f0d9ffd36fe7c619","repo":"moeru-ai/airi","slug":"web-speech-api-recognition-start-failed","errorCode":null,"errorMessage":"Web Speech API recognition start failed:","messagePattern":"Web Speech API recognition start failed:","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/stage-ui/src/libs/providers/providers/browser-web-speech-api/provider.ts","lineNumber":382,"sourceCode":"    newRecognition.onend = sourceRecognition.onend\n    recognitionInstance = newRecognition\n    newRecognition.start()\n    return newRecognition\n  }\n\n  function startRecognition() {\n    try {\n      recognition.start()\n      console.info('Web Speech API recognition started successfully')\n      return true\n    }\n    catch (error: any) {\n      // Common errors:\n      // - \"already started\": Recognition is already running\n      // - \"not-allowed\": Microphone permission denied\n      // - \"service-not-allowed\": Service not available\n      const errorMessage = error?.message || String(error)\n      console.warn('Web Speech API recognition start failed:', errorMessage, error)\n\n      if (errorMessage.includes('already') || errorMessage.includes('started')) {\n        // Recognition is already running, this is OK\n        console.info('Web Speech API recognition already running')\n        return true\n      }\n\n      if (errorMessage.includes('not-allowed') || errorMessage.includes('permission')) {\n        // Permission denied - user needs to grant microphone access\n        const err = new Error('Microphone permission denied. Please grant microphone access and try again.')\n        console.error('Web Speech API: Microphone permission denied')\n        fullStreamCtrl?.error(err)\n        textStreamCtrl?.error(err)\n        deferredText.reject(err)\n        deferredText.isRejected = true\n        return false\n      }\n","sourceCodeStart":364,"sourceCodeEnd":400,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui/src/libs/providers/providers/browser-web-speech-api/provider.ts#L364-L400","documentation":"startRecognition()'s initial recognition.start() threw synchronously. The handler classifies the message: 'already started' is treated as OK (returns true), 'not-allowed'/'permission' fails the stream with a permission error, and anything else triggers the new-instance retry. Typical exceptions are InvalidStateError (already running) and NotAllowedError (permission denied).","triggerScenarios":"Calling start() while recognition is already active; microphone permission denied at prompt; 'service-not-allowed' when the browser's speech service is blocked; start() called from a context without user activation on browsers that require it.","commonSituations":"Double-invoking listen (UI race) so start runs twice; first-run permission prompt declined; enterprise policy blocking the speech service; autoplay/user-activation restrictions in embedded WebViews.","solutions":["If the log says 'already running' right after, it is benign — guard against duplicate start calls in the UI instead.","Grant microphone permission for the site/app and retry from a user gesture.","Check enterprise/browser policies for speech recognition service availability.","For repeated non-permission failures, let the new-instance retry path run; if it also fails, capture the restartError message for support."],"exampleFix":"// before\nfunction startRecognition() {\n  try {\n    recognition.start()\n    return true\n  }\n  catch (error: any) {\n    const errorMessage = error?.message || String(error)\n    ...\n  }\n}\n\n// after — check running state before attempting start\nfunction startRecognition() {\n  if (isRecognitionActive()) {\n    console.info('Web Speech API recognition already running')\n    return true\n  }\n  try {\n    recognition.start()\n    return true\n  }\n  catch (error: any) {\n    const errorMessage = error?.message || String(error)\n    ...\n  }\n}","handlingStrategy":"try-catch","validationCode":"function isRecognitionActive(): boolean {\n  return recognitionActive // flag maintained in recognition.onstart/onend\n}\nif (isRecognitionActive()) {\n  // skip start(); already running\n}","typeGuard":"function isPermissionError(error: unknown): boolean {\n  const msg = errorMessageFrom(error)\n  return msg.includes('not-allowed') || msg.includes('permission')\n}","tryCatchPattern":"try {\n  recognition.start()\n}\ncatch (error: any) {\n  const msg = error?.message || String(error)\n  if (msg.includes('already') || msg.includes('started')) return true\n  if (isPermissionError(msg)) {\n    // surface permission guidance to the user; do not retry\n    return false\n  }\n  // else: new-instance retry path\n}","preventionTips":["Only call start() from a user gesture after permission is granted.","Maintain a running flag to avoid double-start InvalidStateError.","Pre-check getUserMedia permission before enabling the browser speech provider."],"tags":["web-speech-api","invalid-state-error","microphone-permission","start-failed"],"backgroundTag":"speech-recognition-start-failure","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}