jamiepine/voicebox · error · HTTPException

Generation must be completed to regenerate

Error message

Generation must be completed to regenerate

What it means

Returned as HTTP 400 by POST /generate/{generation_id}/regenerate. The guard `(gen.status or 'completed') != 'completed'` only permits regenerate when status is 'completed' (NULL also defaults to 'completed' and is allowed). Regenerate needs finished audio to re-run and produce a new version; 'failed' or 'generating' rows are rejected.

Source

Thrown at backend/routes/generations.py:200

            instruct=gen.instruct,
            mode="retry",
        )
    )

    return models.GenerationResponse.model_validate(gen)


@router.post(
    "/generate/{generation_id}/regenerate",
    response_model=models.GenerationResponse,
)
async def regenerate_generation(generation_id: str, db: Session = Depends(get_db)):
    """Re-run TTS with the same parameters and save the result as a new version."""
    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") != "completed":
        raise HTTPException(status_code=400, detail="Generation must be completed to regenerate")

    gen.status = "generating"
    gen.error = None
    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,
    )

    version_id = str(uuid.uuid4())

    enqueue_generation(
        generation_id,
        run_generation(

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Only show 'regenerate' when generation.status === 'completed' (or NULL treated as completed); show 'retry' for 'failed'.
  2. After a regenerate call, optimistically set status to 'generating' and disable the button until completion.
  3. Refresh status before the call to catch rows that changed state since page load.
  4. For 'failed' rows, route the user to POST /generate/{id}/retry instead.

Example fix

// before
regenBtn.onclick = () => post(`/generate/${id}/regenerate`);
// after
const done = generation.status === 'completed' || generation.status == null;
regenBtn.disabled = !done;
regenBtn.onclick = async () => {
  await post(`/generate/${id}/regenerate`);
  generation.status = 'generating';
  regenBtn.disabled = true;
};
Defensive patterns

Strategy: validation

Validate before calling

status = (gen.status or 'completed')
if status != 'completed':
    raise NotRegeneratable(status)  # would 400
# safe to POST /generate/{id}/regenerate

Type guard

def is_regeneratable(gen) -> bool:
    return (getattr(gen, 'status', None) or 'completed') == 'completed'

Try / catch

try:
    client.post(f'/generate/{generation_id}/regenerate')
except HTTPStatusError as e:
    if e.response.status_code == 400 and 'completed' in e.response.json()['detail']:
        if current_status(generation_id) == 'failed':
            client.post(f'/generate/{generation_id}/retry')  # route failed -> retry
        else:
            wait_for_status(generation_id, 'completed')
        return
    raise

Prevention

When it happens

Trigger: Regenerating a 'failed' generation (use retry instead); regenerating a 'generating' generation still in flight; regenerating a row with an unexpected custom status value.

Common situations: User clicks regenerate on a failed generation instead of retry; double-submit where the first call moved status to 'generating'; custom workflow statuses injected outside the normal state machine; UI showing regenerate for non-completed rows.

Related errors


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