jamiepine/voicebox · error · HTTPException

Invalid reorder request - ensure all generation IDs belong t

Error message

Invalid reorder request - ensure all generation IDs belong to this story

What it means

Returned by PUT /stories/{story_id}/items/reorder when stories.reorder_story_items returns None (HTTP 400). The handler uses None (distinguished from an empty list) to signal that the supplied generation_ids do not all belong to the story, so the reorder is rejected entirely — partial reorders are not applied.

Source

Thrown at backend/routes/stories.py:120

    db: Session = Depends(get_db),
):
    """Update story item timecodes."""
    success = await stories.update_story_item_times(story_id, data, db)
    if not success:
        raise HTTPException(status_code=400, detail="Invalid timecode update request")
    return {"message": "Item timecodes updated successfully"}


@router.put("/stories/{story_id}/items/reorder", response_model=list[models.StoryItemDetail])
async def reorder_story_items(
    story_id: str,
    data: models.StoryItemReorder,
    db: Session = Depends(get_db),
):
    """Reorder story items and recalculate timecodes."""
    items = await stories.reorder_story_items(story_id, data.generation_ids, db)
    if items is None:
        raise HTTPException(
            status_code=400, detail="Invalid reorder request - ensure all generation IDs belong to this story"
        )
    return items


@router.put("/stories/{story_id}/items/{item_id}/move", response_model=models.StoryItemDetail)
async def move_story_item(
    story_id: str,
    item_id: str,
    data: models.StoryItemMove,
    db: Session = Depends(get_db),
):
    """Move a story item (update position and/or track)."""
    item = await stories.move_story_item(story_id, item_id, data, db)
    if item is None:
        raise HTTPException(status_code=404, detail="Story item not found")
    return item

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Rebuild the full ordered generation_ids list from the current GET /stories/{story_id} detail before reordering.
  2. Ensure the reorder payload is a permutation of exactly the story's current item generation ids (no missing, no extra).
  3. After any split/trim/move, refresh the detail so ids are current.
  4. Validate set equality client-side: same set as the story's items.

Example fix

// before
await api.put(`/stories/${sid}/items/reorder`, { generation_ids: order });

// after
const current = (await getStory(sid)).items.map(i => i.generation_id);
if (new Set([...order, ...current]).size !== current.length) throw new Error('order set mismatch');
await api.put(`/stories/${sid}/items/reorder`, { generation_ids: order });
Defensive patterns

Strategy: validation

Validate before calling

function validateReorder(order, storyItems) {
  const current = storyItems.map(i => i.generation_id).sort();
  const sent = [...order].sort();
  if (current.length !== sent.length) throw new Error('count mismatch');
  for (let i = 0; i < current.length; i++) if (current[i] !== sent[i]) throw new Error('set mismatch');
}

Type guard

function isReorder(v) { return Array.isArray(v) && v.every(x => typeof x === 'string'); }

Try / catch

try { await api.put(`/stories/${sid}/items/reorder`, { generation_ids: order }); }
catch (e) { if (e.response?.status === 400) { await refreshStory(sid); rebuildOrder(); } throw e; }

Prevention

When it happens

Trigger: Reorder request whose generation_ids include an id not in the story, or omit some/all current items. The service validates the set matches the story's items and returns None on mismatch.

Common situations: Client reorder list built from a stale story view. Split/trim created new item ids not reflected in the client. Drag-and-drop UI that dropped an item from the ordering.

Related errors


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