{"record":{"id":"06b3b0efc452d924","repo":"odysseus-dev/odysseus","slug":"synthesis-failed","errorCode":null,"errorMessage":"Synthesis failed","messagePattern":"Synthesis failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/settings.js","lineNumber":944,"sourceCode":"            var target = voiceVal.toLowerCase();\n            var match = voices.find(function(v) { return v.name.toLowerCase() === target; }) ||\n                        voices.find(function(v) { return v.name.toLowerCase().includes(target); });\n            if (match) utt.voice = match;\n          }\n          utt.rate = parseFloat(speedSelect.value) || 1;\n          previewBtn.textContent = 'Stop'; previewBtn.style.borderColor = 'var(--red, #e55)';\n          await new Promise(function(resolve, reject) {\n            utt.onend = resolve;\n            utt.onerror = function(e) { reject(new Error('Browser TTS: ' + e.error)); };\n            window.speechSynthesis.speak(utt);\n          });\n        } else {\n          var res = await fetch('/api/tts/synthesize', {\n            method: 'POST', credentials: 'same-origin',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ text: testText, format: 'audio' })\n          });\n          if (!res.ok) { var err = await res.json().catch(function() { return {}; }); throw new Error(err.detail?.message || 'Synthesis failed'); }\n          var blob = await res.blob();\n          var url = URL.createObjectURL(blob);\n          previewAudio = new Audio(url);\n          previewBtn.textContent = 'Stop'; previewBtn.style.borderColor = 'var(--red, #e55)';\n          await new Promise(function(resolve, reject) {\n            previewAudio.onended = function() { URL.revokeObjectURL(url); previewAudio = null; resolve(); };\n            previewAudio.onerror = function() { URL.revokeObjectURL(url); previewAudio = null; reject(new Error('Playback failed')); };\n            previewAudio.play().catch(reject);\n          });\n        }\n      } catch (e) {\n        ttsMsg.textContent = 'Preview failed: ' + e.message; ttsMsg.style.color = 'var(--red, #e55)';\n        setTimeout(function() { ttsMsg.textContent = ''; }, 3000);\n      } finally {\n        resetPreview();\n      }\n    });\n  }","sourceCodeStart":926,"sourceCodeEnd":962,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/settings.js#L926-L962","documentation":"Fallback error when server-side TTS preview fails without a structured detail message. The code POSTs {text, format:'audio'} to /api/tts/synthesize; on non-OK it tries to read err.detail?.message and falls back to 'Synthesis failed' when the body isn't the expected JSON shape.","triggerScenarios":"POST /api/tts/synthesize returns non-2xx with an empty or differently-shaped body (plain-text 500 traceback, HTML error page from a proxy, 502/504 from a gateway) — err.detail?.message is undefined so the generic message is used.","commonSituations":"TTS provider (e.g. ElevenLabs/other) API key missing or invalid; provider quota exhausted; reverse proxy intercepting the request; backend TTS subsystem not configured; network layer returning HTML error pages.","solutions":["Check the response status and raw body of the failing request in devtools","Verify the TTS provider credentials/config on the server","If a proxy returns HTML/empty bodies, fix the proxy or make the client accept text bodies","Improve the fallback to include res.status so users see which failure occurred"],"exampleFix":"// before\n          if (!res.ok) { var err = await res.json().catch(function() { return {}; }); throw new Error(err.detail?.message || 'Synthesis failed'); }\n\n// after\n          if (!res.ok) {\n            var err = await res.json().catch(function() { return {}; });\n            throw new Error(err.detail?.message || 'Synthesis failed (HTTP ' + res.status + ')');\n          }","handlingStrategy":"try-catch","validationCode":"if (!prov || prov === 'disabled') return;\nif (!navigator.onLine) { ttsMsg.textContent = 'Offline'; return; }","typeGuard":null,"tryCatchPattern":"try { const res = await fetch('/api/tts/synthesize', {...}); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.detail?.message || `Synthesis failed (HTTP ${res.status})`); } ... } catch (e) { ttsMsg.textContent = 'Preview failed: ' + e.message; }","preventionTips":["Always pair the fallback message with res.status","Verify server TTS provider credentials before offering preview","Handle proxy/HTML error bodies via the json().catch fallback","Revoke object URLs in a finally to avoid leaks on repeated failures"],"tags":["tts","network","fetch","settings","error-handling"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}