{"record":{"id":"0995ef0e98f97436","repo":"block/buzz","slug":"audio-fetch-failed-response-status","errorCode":null,"errorMessage":"Audio fetch failed (${response.status})","messagePattern":"Audio fetch failed \\((.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"desktop/src/features/messages/ui/AudioMessageAttachment.tsx","lineNumber":79,"sourceCode":"\nfunction audioMimeForUrl(url: string): string {\n  const pathname = url.split(\"?\", 1)[0]?.toLowerCase() ?? \"\";\n  if (pathname.endsWith(\".mp4\")) return \"audio/mp4\";\n  if (pathname.endsWith(\".mp3\")) return \"audio/mpeg\";\n  if (pathname.endsWith(\".ogg\")) return \"audio/ogg\";\n  return \"audio/wav\";\n}\n\nfunction isAbortError(error: unknown): boolean {\n  return error instanceof DOMException && error.name === \"AbortError\";\n}\n\nasync function decodeSamples(\n  url: string,\n  signal: AbortSignal,\n): Promise<Float32Array> {\n  const response = await fetch(url, { signal });\n  if (!response.ok) throw new Error(`Audio fetch failed (${response.status})`);\n  const bytes = await response.arrayBuffer();\n  if (signal.aborted) {\n    throw new DOMException(\"Audio decode cancelled\", \"AbortError\");\n  }\n  const context = new AudioContext();\n  let rejectCancellation: ((reason?: unknown) => void) | undefined;\n  const cancellation = new Promise<never>((_resolve, reject) => {\n    rejectCancellation = reject;\n  });\n  const onAbort = () => {\n    void context.close().catch(() => undefined);\n    rejectCancellation?.(\n      new DOMException(\"Audio decode cancelled\", \"AbortError\"),\n    );\n  };\n  signal.addEventListener(\"abort\", onAbort, { once: true });\n  try {\n    const buffer = await Promise.race([","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/desktop/src/features/messages/ui/AudioMessageAttachment.tsx#L61-L97","documentation":"decodeSamples fetches audio bytes over HTTP and decodes them via AudioContext. If the fetch response status is not ok (non-2xx), it throws `Audio fetch failed (<status>)`. This surfaces HTTP-level failures (404, 403, 500) from the media/attachment URL as a regular Error.","triggerScenarios":"fetch() of an audio attachment URL returns 404 (attachment deleted), 403 (auth/expired signed URL), or 5xx (relay/media server down). Any non-ok response to the audio blob request inside the component's load path.","commonSituations":"Blossom media server missing the blob; expired or invalid signed media URL; relay restarted and media endpoint unreachable; CDN serving 502.","solutions":["Verify the audio URL is valid and the blob still exists on the media server","Check the media endpoint's auth/signing requirements — refresh the URL if it carries an expiring signature","Confirm the relay/Blossom service is running and reachable; retry the fetch","Catch this error in load() and render a 'playback unavailable' state for the attachment"],"exampleFix":"// before\nconst response = await fetch(url, { signal });\nif (!response.ok) throw new Error(`Audio fetch failed (${response.status})`);\n// after\nconst response = await fetch(url, { signal });\nif (!response.ok) {\n  if (response.status >= 500 && retriesLeft > 0) return decodeSamples(url, signal, retriesLeft - 1);\n  throw new Error(`Audio fetch failed (${response.status})`);\n}","handlingStrategy":"try-catch","validationCode":"// preflight: HEAD the URL before decoding\nconst head = await fetch(url, { method: 'HEAD' });\nif (!head.ok) throw new Error(`Audio unavailable (${head.status})`);","typeGuard":"null","tryCatchPattern":"try {\n  const samples = await decodeSamples(url, signal);\n} catch (e) {\n  if ((e as DOMException).name === 'AbortError') return;\n  showPlaybackUnavailable((e as Error).message); // includes status code\n}","preventionTips":["Render a fallback UI when audio bytes are unavailable","Refresh expiring signed media URLs before fetching","Monitor media server 5xx rates to catch backend issues early"],"tags":["network","http","audio","fetch"],"backgroundTag":"http-non-ok-response","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-09-05T18:13:50.666Z","contentChangedAt":"2026-09-05T18:13:50.666Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}