jamiepine/voicebox · warning · HTTPException

Generation has no audio file

Error message

Generation has no audio file

What it means

Returned by GET /history/{generation_id}/export-audio when the generation row exists but `generation.audio_path` is falsy (None or empty string). This means the row was created without an audio file — e.g. a failed generation that never produced output, or an imported non-audio row. HTTP 404.

Source

Thrown at backend/routes/history.py:176

    return StreamingResponse(
        io.BytesIO(zip_bytes),
        media_type="application/zip",
        headers={"Content-Disposition": safe_content_disposition("attachment", filename)},
    )


@router.get("/history/{generation_id}/export-audio")
async def export_generation_audio(
    generation_id: str,
    db: Session = Depends(get_db),
):
    """Export only the audio file from a generation."""
    generation = db.query(DBGeneration).filter_by(id=generation_id).first()
    if not generation:
        raise HTTPException(status_code=404, detail="Generation not found")

    if not generation.audio_path:
        raise HTTPException(status_code=404, detail="Generation has no audio file")

    audio_path = config.resolve_storage_path(generation.audio_path)
    if audio_path is None or not audio_path.is_file():
        raise HTTPException(status_code=404, detail="Audio file not found")

    safe_text = "".join(c for c in generation.text[:30] if c.isalnum() or c in (" ", "-", "_")).strip()
    if not safe_text:
        safe_text = "generation"
    # Append a short id so exports of similarly-worded generations don't collide
    # on the same filename (the first 30 chars are frequently identical).
    filename = f"{safe_text}-{generation_id[:8]}.wav"

    return FileResponse(
        audio_path,
        media_type="audio/wav",
        headers={"Content-Disposition": safe_content_disposition("attachment", filename)},
    )

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Hide/disable the audio-export action unless the generation has a non-empty audio_path and a terminal status.
  2. On 404 'Generation has no audio file.', show a user-facing message rather than retrying.
  3. Filter the history list so rows without audio_path can't trigger this endpoint.

Example fix

// before
<button onClick={() => downloadAudio(id)}>Download</button>

// after
<button disabled={!row.audio_path || !['completed'].includes(row.status)}
        onClick={() => downloadAudio(id)}>Download</button>
Defensive patterns

Strategy: validation

Validate before calling

if (!row.audio_path) { /* hide audio-export action */ return; }
if (!['completed'].includes(row.status)) { /* not ready */ return; }
await fetch(`/history/${id}/export-audio`);

Type guard

function hasAudioPath(row) {
  return row != null && typeof row.audio_path === 'string' && row.audio_path.length > 0;
}

Try / catch

try {
  const res = await fetch(`/history/${id}/export-audio`);
  if (res.status === 404) {
    const d = await res.json();
    if (/no audio file/i.test(d.detail)) { alert('No audio for this generation'); return; }
  }
} catch (e) { console.error(e); }

Prevention

When it happens

Trigger: Exporting audio for a generation whose status is 'failed' (no audio written); a row created for metadata-only purposes; a generation still in progress (loading_model/generating) that hasn't written audio yet.

Common situations: User clicks 'download audio' on a failed or in-progress generation; legacy rows from before audio_path was populated.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/3d7da069d37d20fd. Report an issue: GitHub.