jamiepine/voicebox · error · HTTPException

Audio file not found

Error message

Audio file not found

What it means

Returned by GET /history/{generation_id}/export-audio when `config.resolve_storage_path(generation.audio_path)` returns None OR the resolved path does not satisfy `.is_file()`. This is the case where the DB row points at an audio file, but the file is absent from disk — an orphaned reference. HTTP 404.

Source

Thrown at backend/routes/history.py:180

    )


@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. Verify config.get_generations_dir() / the storage root points at the same volume the rows were written to.
  2. If audio was lost, delete the orphaned generation row or re-generate it.
  3. Run a consistency sweep: for each row with audio_path, confirm the file exists; clean orphans.
  4. Keep the storage root stable across app updates to avoid path resolution failures.

Example fix

// before: assume file exists
window.open(`/history/${id}/export-audio`);

// after: health-check storage on startup, surface orphan rows
for (const row of history) {
  if (row.audio_path && !await fileExists(row.audio_path)) flagOrphan(row.id);
}
Defensive patterns

Strategy: validation

Validate before calling

// Client cannot check disk; guard on status+path, and run server-side consistency checks
if (!hasAudioPath(row)) return;
// Server-side periodic job:
// for row in generations: assert resolve_storage_path(row.audio_path).is_file()

Type guard

function pointsToExistingAudioSync(row, existsFn) {
  return row != null && typeof row.audio_path === 'string' && existsFn(row.audio_path);
}

Try / catch

try {
  const res = await fetch(`/history/${id}/export-audio`);
  if (res.status === 404) {
    const d = await res.json();
    if (/audio file not found/i.test(d.detail)) { flagOrphan(id); return; }
  }
} catch (e) { console.error(e); }

Prevention

When it happens

Trigger: The audio file was deleted/moved from the generations directory while the DB row remains; the storage path config changed so resolve_storage_path can no longer map the stored relative path; the file is on an unmounted external volume.

Common situations: Manual cleanup of the generations dir; migrations that moved storage without updating rows; relative-path mismatch after the configured base dir changed; unmounted network storage.

Related errors


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