jamiepine/voicebox · error · HTTPException
Only failed generations can be retried
Error message
Only failed generations can be retried
What it means
Returned as HTTP 400 by POST /generate/{generation_id}/retry. The guard `(gen.status or 'completed') != 'failed'` only allows retry when the row's status is exactly 'failed'. A NULL status defaults to 'completed' (so it is not retryable), and any other status ('generating', 'completed', etc.) is also rejected. Retry is exclusively for failed generations.
Source
Thrown at backend/routes/generations.py:156
instruct=data.instruct,
mode="generate",
max_chunk_chars=data.max_chunk_chars,
crossfade_ms=data.crossfade_ms,
)
)
return generation
@router.post("/generate/{generation_id}/retry", response_model=models.GenerationResponse)
async def retry_generation(generation_id: str, db: Session = Depends(get_db)):
"""Retry a failed generation using the same parameters."""
gen = db.query(DBGeneration).filter_by(id=generation_id).first()
if not gen:
raise HTTPException(status_code=404, detail="Generation not found")
if (gen.status or "completed") != "failed":
raise HTTPException(status_code=400, detail="Only failed generations can be retried")
gen.status = "generating"
gen.error = None
gen.audio_path = ""
gen.duration = 0
db.commit()
db.refresh(gen)
task_manager = get_task_manager()
task_manager.start_generation(
task_id=generation_id,
profile_id=gen.profile_id,
text=gen.text,
)
enqueue_generation(
generation_id,
run_generation(View on GitHub (pinned to 51f49dea19)
Solutions
- Only show the retry action when generation.status === 'failed'.
- After a successful retry call, optimistically mark the row 'generating' in the UI and disable retry.
- Refresh the generation status before retry to catch rows that recovered or are in flight.
- For NULL-status rows, treat them as completed and offer regenerate, not retry.
Example fix
// before
retryBtn.onclick = () => post(`/generate/${id}/retry`);
// after
retryBtn.disabled = generation.status !== 'failed';
retryBtn.onclick = async () => {
await post(`/generate/${id}/retry`);
generation.status = 'generating'; // optimistic
retryBtn.disabled = true;
}; Defensive patterns
Strategy: validation
Validate before calling
status = (gen.status or 'completed')
if status != 'failed':
raise NotRetryable(status) # would 400
# safe to POST /generate/{id}/retry Type guard
def is_retryable(gen) -> bool:
return (getattr(gen, 'status', None) or 'completed') == 'failed' Try / catch
try:
client.post(f'/generate/{generation_id}/retry')
except HTTPStatusError as e:
if e.response.status_code == 400 and 'failed' in e.response.json()['detail']:
refresh_generation_status(generation_id) # status changed since page load
return
raise Prevention
- Only show retry when status === 'failed'.
- After a retry call, optimistically mark status 'generating' and disable retry.
- Refresh status before retry to catch rows that recovered or are in flight.
- Treat NULL-status rows as completed — offer regenerate, not retry.
When it happens
Trigger: Retrying a generation whose status is 'completed' (already succeeded), 'generating' (still running), or NULL (treated as completed); retrying after a successful retry already flipped status to 'generating'.
Common situations: User clicks retry on a generation that has since succeeded; double-click on retry where the first call moved status to 'generating'; a legacy row with NULL status that the UI mislabels as failed; retrying a generation that is mid-flight.
Related errors
- Generation must be completed to regenerate
- {e}
- Generation not found
- {exception message from delete_preset (ValueError)}
- Cannot delete the last remaining version
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/3a8e7b2de4417691.
Report an issue: GitHub.