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

  1. If the row might be orphaned, query the generations table directly (without the profile join) to confirm existence.
  2. Use a LEFT OUTER JOIN instead of INNER JOIN in this query so orphaned generations still resolve (profile_name becomes None).
  3. When deleting a profile, either cascade-delete its generations or reassign/null their profile_id consistently.
  4. 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

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


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