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 updateView on GitHub (pinned to f9235ebbf1)
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
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
- 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)
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
- Failed to save note
- Failed to trigger task (${res.status})
- Failed to stop task (${res.status})
- Failed to revert task
- HTTP ${res.status}
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/853fe40d26d47209.
Report an issue: GitHub.