moeru-ai/airi · error · Error

Streaming transcription request failed with status

Error message

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

What it means

Thrown by the shared streaming-transcription helper when the POST that uploads the raw audio ReadableStream (fetch with `duplex: 'half'`) returns a non-2xx status. The helper is provider-generic: `requestTarget`, headers, and `abortSignal` all come from the caller's options, so the status reflects whichever SSE transcription backend was configured.

Solutions

  1. Log `response.status` from the message and map it: 401/403 → fix the provider credentials/headers; 400 → correct the audio `Content-Type`; 413 → shorten or transcode the audio; 404 → fix the request URL.
  2. curl the same target with a small audio file and the same headers to reproduce outside the app.
  3. Verify the provider endpoint actually speaks the expected SSE transcription protocol.
  4. If the status is transient (429/5xx), retry with backoff before surfacing the failure.

Example fix

// before
if (!response.ok)
  throw new Error(`Streaming transcription request failed with status ${response.status}`)
// after — include the upstream body so the cause is diagnosable
if (!response.ok) {
  const detail = await response.text().catch(() => '')
  throw new Error(`Streaming transcription request failed with status ${response.status}${detail ? `: ${detail.slice(0, 256)}` : ''}`)
}
Defensive patterns

Strategy: try-catch

Validate before calling

options.abortSignal?.throwIfAborted()
if (!options.headers?.Authorization && !options.headers?.['x-api-key'])
  throw new Error('Transcription provider credentials are required')

Try / catch

try {
  await startStreamingTranscription(options)
}
catch (error) {
  const status = /status (\d{3})/.exec(errorMessageFrom(error))?.[1]
  if (status === '401' || status === '403')
    // refresh provider credentials
  else if (status === '429' || status === '5xx')
    // backoff + retry with the same abort signal
  else
    throw error
}

Prevention

When it happens

Trigger: 401/403 when the provider-specific auth header is missing/expired; 400 for an unsupported `Content-Type` on the audio stream; 413 when the audio stream exceeds the provider limit; 404 from a wrong `requestTarget` URL; 5xx upstream outage — all before any SSE parsing begins.

Common situations: Provider base URL typo'd in settings; audio sent with a content type the backend rejects (e.g. raw PCM labeled `audio/webm`); browser fetch rejecting the stream body silently behind a proxy that answers 4xx; token refresh racing the upload start.

Related errors


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

Appendix: source

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

  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,
        // Browser fetch requires half-duplex mode for a ReadableStream body.
        // Keep this at the transport boundary so every SSE transcription
        // provider receives the required request option.
        duplex: 'half',
        headers: options.headers,
        method: 'POST',
        signal: options.abortSignal,
      } as RequestInit & { duplex: 'half' })

      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 677329427f)