nexu-io/open-design · error · Error
openai speech returned zero bytes
Error message
openai speech returned zero bytes
What it means
Thrown after the speech endpoint returns HTTP 2xx but the body decodes to zero bytes (`bytes.length === 0`). It guards against a provider or intermediary that reports success while delivering no audio, which would otherwise write an empty file and look like a successful generation to the agent.
Source
Thrown at apps/daemon/src/media/index.ts:1374
};
if (azure) {
headers['api-key'] = credentials.apiKey;
}
const resp = await fetch(url, withMediaRequestInit(ctx, {
method: 'POST',
headers,
body: JSON.stringify(body),
}));
if (!resp.ok) {
const text = await resp.text();
const tag = azure ? 'azure-openai' : 'openai';
throw new Error(`${tag} speech ${resp.status}: ${truncate(text, 240)}`);
}
const arr = await resp.arrayBuffer();
const bytes = Buffer.from(arr);
if (bytes.length === 0) {
throw new Error('openai speech returned zero bytes');
}
const tag = azure ? 'azure-openai' : 'openai';
const noteBits = [`${tag}/${ctx.wireModel}`, voiceId, `${format}`, `${bytes.length} bytes`];
if (instructions) noteBits.splice(2, 0, 'styled');
return {
bytes,
providerNote: noteBits.join(' · '),
suggestedExt: format === 'opus' ? '.ogg' : `.${format}`,
};
}
// ---------------------------------------------------------------------------
// Provider: Volcengine Ark — Doubao Seedance 2.0 video.
//
// Docs:
// POST /api/v3/contents/generations/tasks → { id }
// GET /api/v3/contents/generations/tasks/{id} → { status, content: { video_url } }
// We submit, poll until succeeded/failed, then fetch the producedView on GitHub (pinned to 5be4028344)
Solutions
- Retry once — transient empty bodies from the provider usually succeed on the next call.
- Confirm the `response_format`/`format` is supported by the model and deployment (`mp3` is universal; `opus`/`aac`/`flac`/`wav`/`pcm` vary by deployment).
- If behind a proxy, bypass it or confirm it passes binary `Content-Type: audio/*` bodies unchanged.
Defensive patterns
Strategy: retry
Try / catch
let lastErr: unknown;
for (let attempt = 0; attempt < 2; attempt++) {
try { return await renderOpenAISpeech(ctx, credentials); }
catch (err) {
lastErr = err;
if (/zero bytes/.test(err instanceof Error ? err.message : '')) continue;
throw err;
}
}
throw lastErr; Prevention
- Treat a 200-with-empty-body as retryable; one retry clears most transient cases.
- Confirm the requested `response_format` is supported by the deployment before relying on it.
- If behind a proxy, verify it forwards binary `audio/*` bodies unchanged.
When it happens
Trigger: A 2xx response whose `arrayBuffer()` resolves to length 0. Seen with a misconfigured reverse proxy/gateway stripping the binary `audio/*` body, an Azure deployment returning `200` with no audio for an unsupported `response_format`, or an edge content-moderation path that yields no audio without an error status.
Common situations: (1) A corporate proxy re-encoding and emptying the audio body; (2) requesting an unsupported `response_format` (e.g. `opus` against a deployment that only serves `mp3`); (3) transient provider hiccup returning a 200 with a truncated body.
Related errors
- ${tag} speech ${resp.status}: ${truncate(text, 240)}
- no OpenAI credential — configure an API key in Settings or s
- xai tts response had zero bytes
- no OpenAI credential - configure an API key in Settings or s
- ${tag} ${resp.status}: ${truncate(text, 240)}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/15d959528d24a596.
Report an issue: GitHub.