{"record":{"id":"2bd0fa812d52d72f","repo":"moeru-ai/airi","slug":"streaming-transcription-request-failed-with-status","errorCode":null,"errorMessage":"Streaming transcription request failed with status ${response.status}","messagePattern":"Streaming transcription request failed with status (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/libs/providers/stream-transcription/index.ts","lineNumber":132,"sourceCode":"    start(controller) {\n      textStreamCtrl = controller\n    },\n  })\n\n  void (async () => {\n    try {\n      const requestTarget = options.baseURL instanceof URL\n        ? options.baseURL\n        : new URL(typeof options.baseURL === 'string' ? options.baseURL : 'http://localhost')\n      const response = await fetcher(requestTarget, {\n        body: audioStream,\n        headers: options.headers,\n        method: 'POST',\n        signal: options.abortSignal,\n      })\n\n      if (!response.ok)\n        throw new Error(`Streaming transcription request failed with status ${response.status}`)\n\n      if (!response.body)\n        throw new Error('Streaming transcription response is missing a readable body.')\n\n      await response.body\n        .pipeThrough(createSSETransformer())\n        .pipeTo(new WritableStream<AIRIStreamTranscriptionDelta>({\n          write: (chunk) => {\n            fullStreamCtrl?.enqueue(chunk)\n            if (chunk.type === 'transcript.text.delta') {\n              text += chunk.delta\n              textStreamCtrl?.enqueue(chunk.delta)\n            }\n            else if (chunk.type === 'transcript.text.snapshot') {\n              text = chunk.text\n            }\n          },\n          close: () => {","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-ui/src/libs/providers/stream-transcription/index.ts#L114-L150","documentation":"Thrown inside streamTranscription's async pump when the POST to the transcription baseURL returns a non-ok status. The adapter streams the audio body and expects an SSE response; on failure it aborts before piping, erroring both the fullStream and textStream controllers and rejecting the text promise. The HTTP status is included so the caller can distinguish auth vs server errors.","triggerScenarios":"The fetcher (options.fetch or globalThis.fetch) POSTs audioStream to options.baseURL with options.headers and gets a non-2xx. Typical causes: 401/403 (missing or invalid Authorization header), 404 (wrong baseURL / transcription route not deployed), 413 (audio body too large), 415 (missing or wrong Content-Type header), 500/502/503 (upstream STT provider down).","commonSituations":"baseURL points at the wrong server or a path that is not a transcription endpoint; the auth token is absent or expired so headers lack a valid Bearer; the upstream STT provider (e.g. the configured Hearing backend) is temporarily unavailable; sending raw PCM without the Content-Type the server expects.","solutions":["Confirm options.baseURL is the correct transcription endpoint and options.headers include a valid Authorization header.","Map the embedded status: 401/403 -> re-authenticate; 404 -> fix baseURL; 5xx -> retry with backoff or report upstream outage.","Ensure the audio stream encoding matches what the server expects (set Content-Type in options.headers appropriately).","Handle the rejection on result.text / result.fullStream and surface a user-facing 'transcription unavailable' state."],"exampleFix":"// caller-side handling of the async failure\nconst result = streamTranscription({ baseURL, headers, inputAudioStream })\ntry {\n  const transcript = await result.text\n  // use transcript\n}\ncatch (error) {\n  console.error('Transcription failed:', error)\n  // show 'speech recognition unavailable' in the UI\n}","handlingStrategy":"try-catch","validationCode":"function validateTranscriptionRequest(options: StreamTranscriptionOptions): string | null {\n  if (!options.baseURL)\n    return 'baseURL is required for transcription'\n  try {\n    new URL(typeof options.baseURL === 'string' ? options.baseURL : 'http://localhost')\n  }\n  catch {\n    return `baseURL is not a valid URL: ${options.baseURL}`\n  }\n  if (!options.headers || !(options.headers as Headers).get?.('Authorization')\n      && !(Array.isArray(options.headers) && options.headers.some(([k]) => k.toLowerCase() === 'authorization')))\n    return 'Authorization header is missing; transcription will return 401'\n  return null\n}","typeGuard":"function isTranscriptionHttpError(error: unknown): boolean {\n  return error instanceof Error && /Streaming transcription request failed with status \\d{3}/.test(error.message)\n}\n\nfunction extractStatus(error: unknown): number | undefined {\n  const match = /status (\\d{3})/.exec(String((error as Error)?.message ?? ''))\n  return match ? Number(match[1]) : undefined\n}","tryCatchPattern":"const result = streamTranscription(options)\ntry {\n  for await (const chunk of result.fullStream) {\n    if (chunk.type === 'transcript.text.delta')\n      onDelta(chunk.delta)\n  }\n}\ncatch (error) {\n  const status = extractStatus(error)\n  if (status && (status === 429 || status >= 500)) {\n    // transient; inform user and optionally retry\n  }\n  else if (status === 401 || status === 403) {\n    // re-authenticate\n  }\n  else {\n    // report and degrade gracefully\n  }\n}","preventionTips":["Always include a valid Authorization header in options.headers.","Verify options.baseURL resolves to a deployed transcription route before enabling Hearing.","Consume result.fullStream or await result.text with try/catch; the async pump rejects both."],"tags":["network","http","transcription","streaming","api-key","auth"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}