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
- 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.
- curl the same target with a small audio file and the same headers to reproduce outside the app.
- Verify the provider endpoint actually speaks the expected SSE transcription protocol.
- 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
- Validate the provider endpoint URL and required headers before opening the duplex stream.
- Send an accurate audio Content-Type for the stream.
- Cap audio length to avoid 413 rejections.
- Retry transient statuses once with backoff before surfacing.
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
- Streaming transcription response is missing a readable body.
- MiMo transcription failed
- OpenRouter audio response has no body
- streaming models upstream
- streaming voices upstream
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)