odysseus-dev/odysseus · error · Error

Synthesis failed

Error message

Synthesis failed

What it means

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.

Solutions

  1. Check the response status and raw body of the failing request in devtools
  2. Verify the TTS provider credentials/config on the server
  3. If a proxy returns HTML/empty bodies, fix the proxy or make the client accept text bodies
  4. Improve the fallback to include res.status so users see which failure occurred

Example fix

// before
          if (!res.ok) { var err = await res.json().catch(function() { return {}; }); throw new Error(err.detail?.message || 'Synthesis failed'); }

// after
          if (!res.ok) {
            var err = await res.json().catch(function() { return {}; });
            throw new Error(err.detail?.message || 'Synthesis failed (HTTP ' + res.status + ')');
          }
Defensive patterns

Strategy: try-catch

Validate before calling

if (!prov || prov === 'disabled') return;
if (!navigator.onLine) { ttsMsg.textContent = 'Offline'; return; }

Try / catch

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; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/06b3b0efc452d924. Report an issue: GitHub.

Appendix: source

Thrown at static/js/settings.js:944

            var target = voiceVal.toLowerCase();
            var match = voices.find(function(v) { return v.name.toLowerCase() === target; }) ||
                        voices.find(function(v) { return v.name.toLowerCase().includes(target); });
            if (match) utt.voice = match;
          }
          utt.rate = parseFloat(speedSelect.value) || 1;
          previewBtn.textContent = 'Stop'; previewBtn.style.borderColor = 'var(--red, #e55)';
          await new Promise(function(resolve, reject) {
            utt.onend = resolve;
            utt.onerror = function(e) { reject(new Error('Browser TTS: ' + e.error)); };
            window.speechSynthesis.speak(utt);
          });
        } else {
          var res = await fetch('/api/tts/synthesize', {
            method: 'POST', credentials: 'same-origin',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ text: testText, format: 'audio' })
          });
          if (!res.ok) { var err = await res.json().catch(function() { return {}; }); throw new Error(err.detail?.message || 'Synthesis failed'); }
          var blob = await res.blob();
          var url = URL.createObjectURL(blob);
          previewAudio = new Audio(url);
          previewBtn.textContent = 'Stop'; previewBtn.style.borderColor = 'var(--red, #e55)';
          await new Promise(function(resolve, reject) {
            previewAudio.onended = function() { URL.revokeObjectURL(url); previewAudio = null; resolve(); };
            previewAudio.onerror = function() { URL.revokeObjectURL(url); previewAudio = null; reject(new Error('Playback failed')); };
            previewAudio.play().catch(reject);
          });
        }
      } catch (e) {
        ttsMsg.textContent = 'Preview failed: ' + e.message; ttsMsg.style.color = 'var(--red, #e55)';
        setTimeout(function() { ttsMsg.textContent = ''; }, 3000);
      } finally {
        resetPreview();
      }
    });
  }

View on GitHub (pinned to f9235ebbf1)