{"record":{"id":"853fe40d26d47209","repo":"odysseus-dev/odysseus","slug":"transcription-failed","errorCode":null,"errorMessage":"Transcription failed","messagePattern":"Transcription failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/voiceRecorder.js","lineNumber":123,"sourceCode":"  return _browserTranscript.trim();\n}\n\n/**\n * Send audio to server for transcription\n */\nasync function transcribeOnServer(audioBlob) {\n  const formData = new FormData();\n  formData.append('file', audioBlob, 'audio.webm');\n\n  const res = await fetch('/api/stt/transcribe', {\n    method: 'POST',\n    credentials: 'same-origin',\n    body: formData,\n  });\n\n  if (!res.ok) {\n    const err = await res.json().catch(() => ({}));\n    throw new Error(err.detail?.message || 'Transcription failed');\n  }\n\n  const data = await res.json();\n  return data.text || '';\n}\n\n/**\n * Insert transcribed text into the chat input\n */\nfunction insertTranscription(text, showToast) {\n  if (!text) return;\n  const input = document.getElementById('message');\n  if (!input) return;\n\n  const existing = input.value.trim();\n  input.value = existing ? existing + ' ' + text : text;\n\n  // Trigger auto-resize and icon update","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/voiceRecorder.js#L105-L141","documentation":"Fallback error from transcribeOnServer when POST /api/stt/transcribe fails. The client parses the JSON error body and prefers err.detail.message; this literal appears only when the body is unparseable or lacks detail.message (the .catch(() => ({})) path).","triggerScenarios":"Submitting voice-recorded audio when the STT backend is not configured (e.g. no API key/model), audio format rejected, request too large, session expired (401/403), or the response is a non-JSON error page.","commonSituations":"STT provider credentials missing or exhausted; unsupported audio codec from the browser recorder; reverse proxy body-size limit rejecting the multipart upload; feature flag disabled server-side.","solutions":["Check the response status/body in devtools for the real reason (detail.message is used when present)","Verify the STT backend is configured and its API key/quota valid","Confirm the uploaded webm audio is within any proxy's client_max_body_size","Re-login if 401/403"],"exampleFix":"// before\nconst err = await res.json().catch(() => ({}));\nthrow new Error(err.detail?.message || 'Transcription failed');\n// after — also surface status and flat detail\nconst err = await res.json().catch(() => ({}));\nthrow new Error(err.detail?.message || err.detail || `Transcription failed (${res.status})`);","handlingStrategy":"try-catch","validationCode":"if (!(audioBlob instanceof Blob) || audioBlob.size === 0) { showToast('No audio recorded'); return; }","typeGuard":"function isSttErrorBody(v: unknown): v is { detail?: { message?: string } } { return typeof v === 'object' && v !== null && 'detail' in v; }","tryCatchPattern":"try { const text = await transcribeOnServer(blob); } catch (e) { showToast(e.message.includes('Transcription') ? 'Speech-to-text unavailable' : e.message); }","preventionTips":["Check recorder state produced a non-empty blob before posting","Surface provider-specific detail.message when present","Keep audio under proxy body-size limits (shorter clips)"],"tags":["http","speech-to-text","audio","frontend"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}