jamiepine/voicebox · warning · HTTPException
Generation not found
Error message
Generation not found
What it means
Returned by GET /history/{generation_id} when the query `db.query(DBGeneration, DBVoiceProfile.name).join(DBVoiceProfile, ...).filter(DBGeneration.id == generation_id).first()` returns None. Because this uses an INNER JOIN against voice_profiles, a generation whose profile has been deleted will NOT be returned even though the generation row exists — the join eliminates it. HTTP 404.
Source
Thrown at backend/routes/history.py:86
count = await history.delete_failed_generations(db)
return {"deleted": count}
@router.get("/history/{generation_id}", response_model=models.HistoryResponse)
async def get_generation(
generation_id: str,
db: Session = Depends(get_db),
):
"""Get a generation by ID."""
result = (
db.query(DBGeneration, DBVoiceProfile.name.label("profile_name"))
.join(DBVoiceProfile, DBGeneration.profile_id == DBVoiceProfile.id)
.filter(DBGeneration.id == generation_id)
.first()
)
if not result:
raise HTTPException(status_code=404, detail="Generation not found")
gen, profile_name = result
return models.HistoryResponse(
id=gen.id,
profile_id=gen.profile_id,
profile_name=profile_name,
text=gen.text,
language=gen.language,
audio_path=gen.audio_path,
duration=gen.duration,
seed=gen.seed,
instruct=gen.instruct,
engine=gen.engine or "qwen",
model_size=gen.model_size,
status=gen.status or "completed",
error=gen.error,
is_favorited=bool(gen.is_favorited),
created_at=gen.created_at,View on GitHub (pinned to 51f49dea19)
Solutions
- If the row might be orphaned, query the generations table directly (without the profile join) to confirm existence.
- Use a LEFT OUTER JOIN instead of INNER JOIN in this query so orphaned generations still resolve (profile_name becomes None).
- When deleting a profile, either cascade-delete its generations or reassign/null their profile_id consistently.
- Confirm the generation_id is the exact UUID stored.
Example fix
# before (inner join drops orphaned generations)
result = db.query(DBGeneration, DBVoiceProfile.name.label("profile_name")) \
.join(DBVoiceProfile, DBGeneration.profile_id == DBVoiceProfile.id) \
.filter(DBGeneration.id == generation_id).first()
# after (outer join keeps orphaned rows)
result = db.query(DBGeneration, DBVoiceProfile.name.label("profile_name")) \
.outerjoin(DBVoiceProfile, DBGeneration.profile_id == DBVoiceProfile.id) \
.filter(DBGeneration.id == generation_id).first() Defensive patterns
Strategy: validation
Validate before calling
// Confirm existence (be aware INNER JOIN drops orphaned generations)
const res = await fetch(`/history/${id}`);
if (res.status === 404) { removeFromList(id); return; } Type guard
function isHistoryRow(r) {
return r != null && typeof r.id === 'string' && typeof r.profile_id === 'string';
} Try / catch
try {
const res = await fetch(`/history/${id}`);
if (res.status === 404) { removeFromList(id); return; }
const row = await res.json();
} catch (e) { console.error(e); } Prevention
- Use a LEFT OUTER JOIN server-side so orphaned generations still resolve.
- When deleting a profile, cascade or null profile_id on its generations.
- Run consistency checks between generations and voice_profiles.
When it happens
Trigger: generation_id genuinely absent; OR generation exists but its voice_profiles row was deleted (orphaned generation), causing the INNER JOIN to drop it; profile_id NULL on the generation row.
Common situations: Profile deleted via /profiles/{id} without cascading or nulling the generation's profile_id, orphaning its generations from this join; referencing an old id after a DB reset.
Related errors
- Audio file not found
- Generation has no audio file
- Profile not found
- Generation failed; no audio available
- Sample not found
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/69a0c74d31324f73.
Report an issue: GitHub.