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 produced

View on GitHub (pinned to 5be4028344)

Solutions

  1. Retry once — transient empty bodies from the provider usually succeed on the next call.
  2. Confirm the `response_format`/`format` is supported by the model and deployment (`mp3` is universal; `opus`/`aac`/`flac`/`wav`/`pcm` vary by deployment).
  3. 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

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


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/15d959528d24a596. Report an issue: GitHub.