jamiepine/voicebox · error · Error
HTTP ${res.status}
Error message
HTTP ${res.status} What it means
Thrown by handleExportAudio() after fetching a capture's audio bytes from apiClient.getCaptureAudioUrl(selected.id). If the response is not ok, it throws a generic HTTP <status>. The function is wrapped in try/catch that routes failures to exportToastError(), so the user sees an error toast.
Source
Thrown at app/src/components/CapturesTab/CapturesTab.tsx:345
const exportToastError = (err: unknown) => {
toast({
title: t('captures.toast.exportFailed'),
description: err instanceof Error ? err.message : String(err),
variant: 'destructive',
});
};
const handleExportAudio = async () => {
if (!selected) return;
try {
const dest = await save({
defaultPath: `capture_${selected.id.slice(0, 8)}.wav`,
filters: [{ name: 'Audio', extensions: ['wav'] }],
});
if (!dest) return;
const res = await fetch(apiClient.getCaptureAudioUrl(selected.id));
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const buf = new Uint8Array(await res.arrayBuffer());
await writeFile(dest, buf);
exportToastSuccess(dest);
} catch (err) {
exportToastError(err);
}
};
const handleExportTranscript = async () => {
if (!selected) return;
const text = (selected.transcript_refined || selected.transcript_raw || '').trim();
if (!text) {
toast({ title: t('captures.toast.exportEmpty'), variant: 'destructive' });
return;
}
try {
const dest = await save({
defaultPath: `capture_${selected.id.slice(0, 8)}.txt`,View on GitHub (pinned to 51f49dea19)
Solutions
- Verify the capture has audio before enabling Export Audio (check a has_audio / audio_url field on CaptureResponse).
- Differentiate 404 (show 'audio no longer available') from other statuses in the toast.
- Confirm the backend capture-audio route and on-disk path are intact; check backend logs for the matching read error.
- Ensure the auth token is still valid (re-auth on 401).
Example fix
// before
const res = await fetch(apiClient.getCaptureAudioUrl(selected.id));
if (!res.ok) throw new Error(`HTTP ${res.status}`);
// after
const res = await fetch(apiClient.getCaptureAudioUrl(selected.id));
if (!res.ok) {
throw new Error(res.status === 404 ? 'Audio for this capture is no longer available.' : `HTTP ${res.status}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
function captureHasAudio(c: CaptureResponse): boolean {
return Boolean(c.audio_url || c.has_audio);
}
// Gate export:
if (!captureHasAudio(selected)) return; Try / catch
try {
const res = await fetch(apiClient.getCaptureAudioUrl(selected.id));
if (!res.ok) throw new Error(res.status === 404 ? 'Audio no longer available.' : `HTTP ${res.status}`);
await writeFile(dest, new Uint8Array(await res.arrayBuffer()));
exportToastSuccess(dest);
} catch (err) {
exportToastError(err);
} Prevention
- Disable Export Audio when the capture has no audio_url/has_audio flag.
- Differentiate 404 from other statuses in the user message.
- Re-auth on 401 rather than showing a generic HTTP error.
When it happens
Trigger: GET on the capture-audio URL returns 404 (audio file missing on disk / never persisted), 500 (backend error reading the file), 401/403 (auth/session expired), or a gateway 502/504. Also fails if the backend is unreachable.
Common situations: Captures older than the audio retention window had their WAV pruned. The backend storage volume was wiped/moved. The capture recorded but audio write failed silently (transcript exists, audio does not).
Related errors
- captures.noTranscriptError
- Failed to fetch model info: ${response.status}
- e instanceof Error ? e.message : errorMessage
- HTTP error! status: ${response.status}
- Jupiter HTTP ${res.status}
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/ee415c5df967bd1f.
Report an issue: GitHub.