moeru-ai/airi · error · Error

Streaming transcription request failed with status ${respons

Error message

Streaming transcription request failed with status ${response.status}

What it means

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.

Source

Thrown at packages/stage-ui/src/libs/providers/stream-transcription/index.ts:132

    start(controller) {
      textStreamCtrl = controller
    },
  })

  void (async () => {
    try {
      const requestTarget = options.baseURL instanceof URL
        ? options.baseURL
        : new URL(typeof options.baseURL === 'string' ? options.baseURL : 'http://localhost')
      const response = await fetcher(requestTarget, {
        body: audioStream,
        headers: options.headers,
        method: 'POST',
        signal: options.abortSignal,
      })

      if (!response.ok)
        throw new Error(`Streaming transcription request failed with status ${response.status}`)

      if (!response.body)
        throw new Error('Streaming transcription response is missing a readable body.')

      await response.body
        .pipeThrough(createSSETransformer())
        .pipeTo(new WritableStream<AIRIStreamTranscriptionDelta>({
          write: (chunk) => {
            fullStreamCtrl?.enqueue(chunk)
            if (chunk.type === 'transcript.text.delta') {
              text += chunk.delta
              textStreamCtrl?.enqueue(chunk.delta)
            }
            else if (chunk.type === 'transcript.text.snapshot') {
              text = chunk.text
            }
          },
          close: () => {

View on GitHub (pinned to 27111382b4)

Solutions

  1. Confirm options.baseURL is the correct transcription endpoint and options.headers include a valid Authorization header.
  2. Map the embedded status: 401/403 -> re-authenticate; 404 -> fix baseURL; 5xx -> retry with backoff or report upstream outage.
  3. Ensure the audio stream encoding matches what the server expects (set Content-Type in options.headers appropriately).
  4. Handle the rejection on result.text / result.fullStream and surface a user-facing 'transcription unavailable' state.

Example fix

// caller-side handling of the async failure
const result = streamTranscription({ baseURL, headers, inputAudioStream })
try {
  const transcript = await result.text
  // use transcript
}
catch (error) {
  console.error('Transcription failed:', error)
  // show 'speech recognition unavailable' in the UI
}
Defensive patterns

Strategy: try-catch

Validate before calling

function validateTranscriptionRequest(options: StreamTranscriptionOptions): string | null {
  if (!options.baseURL)
    return 'baseURL is required for transcription'
  try {
    new URL(typeof options.baseURL === 'string' ? options.baseURL : 'http://localhost')
  }
  catch {
    return `baseURL is not a valid URL: ${options.baseURL}`
  }
  if (!options.headers || !(options.headers as Headers).get?.('Authorization')
      && !(Array.isArray(options.headers) && options.headers.some(([k]) => k.toLowerCase() === 'authorization')))
    return 'Authorization header is missing; transcription will return 401'
  return null
}

Type guard

function isTranscriptionHttpError(error: unknown): boolean {
  return error instanceof Error && /Streaming transcription request failed with status \d{3}/.test(error.message)
}

function extractStatus(error: unknown): number | undefined {
  const match = /status (\d{3})/.exec(String((error as Error)?.message ?? ''))
  return match ? Number(match[1]) : undefined
}

Try / catch

const result = streamTranscription(options)
try {
  for await (const chunk of result.fullStream) {
    if (chunk.type === 'transcript.text.delta')
      onDelta(chunk.delta)
  }
}
catch (error) {
  const status = extractStatus(error)
  if (status && (status === 429 || status >= 500)) {
    // transient; inform user and optionally retry
  }
  else if (status === 401 || status === 403) {
    // re-authenticate
  }
  else {
    // report and degrade gracefully
  }
}

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/2bd0fa812d52d72f. Report an issue: GitHub.