odysseus-dev/odysseus · error · Error

Transcription failed

Error message

Transcription failed

What it means

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).

Source

Thrown at static/js/voiceRecorder.js:123

  return _browserTranscript.trim();
}

/**
 * Send audio to server for transcription
 */
async function transcribeOnServer(audioBlob) {
  const formData = new FormData();
  formData.append('file', audioBlob, 'audio.webm');

  const res = await fetch('/api/stt/transcribe', {
    method: 'POST',
    credentials: 'same-origin',
    body: formData,
  });

  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.detail?.message || 'Transcription failed');
  }

  const data = await res.json();
  return data.text || '';
}

/**
 * Insert transcribed text into the chat input
 */
function insertTranscription(text, showToast) {
  if (!text) return;
  const input = document.getElementById('message');
  if (!input) return;

  const existing = input.value.trim();
  input.value = existing ? existing + ' ' + text : text;

  // Trigger auto-resize and icon update

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Check the response status/body in devtools for the real reason (detail.message is used when present)
  2. Verify the STT backend is configured and its API key/quota valid
  3. Confirm the uploaded webm audio is within any proxy's client_max_body_size
  4. Re-login if 401/403

Example fix

// before
const err = await res.json().catch(() => ({}));
throw new Error(err.detail?.message || 'Transcription failed');
// after — also surface status and flat detail
const err = await res.json().catch(() => ({}));
throw new Error(err.detail?.message || err.detail || `Transcription failed (${res.status})`);
Defensive patterns

Strategy: try-catch

Validate before calling

if (!(audioBlob instanceof Blob) || audioBlob.size === 0) { showToast('No audio recorded'); return; }

Type guard

function isSttErrorBody(v: unknown): v is { detail?: { message?: string } } { return typeof v === 'object' && v !== null && 'detail' in v; }

Try / catch

try { const text = await transcribeOnServer(blob); } catch (e) { showToast(e.message.includes('Transcription') ? 'Speech-to-text unavailable' : e.message); }

Prevention

When it happens

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

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

Related errors


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