jamiepine/voicebox · warning · HTTPException

Story item not found

Error message

Story item not found

What it means

Returned by DELETE /stories/{story_id}/items/{item_id} when stories.remove_item_from_story returns falsy (HTTP 404). Either the story or the item (or their combination) was not found, so nothing was removed.

Source

Thrown at backend/routes/stories.py:94

    db: Session = Depends(get_db),
):
    """Add a generation to a story."""
    item = await stories.add_item_to_story(story_id, data, db)
    if not item:
        raise HTTPException(status_code=404, detail="Story or generation not found")
    return item


@router.delete("/stories/{story_id}/items/{item_id}")
async def remove_story_item(
    story_id: str,
    item_id: str,
    db: Session = Depends(get_db),
):
    """Remove a story item from a story."""
    success = await stories.remove_item_from_story(story_id, item_id, db)
    if not success:
        raise HTTPException(status_code=404, detail="Story item not found")
    return {"message": "Item removed successfully"}


@router.put("/stories/{story_id}/items/times")
async def update_story_item_times(
    story_id: str,
    data: models.StoryItemBatchUpdate,
    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(

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Treat 404 on item removal as 'already gone' success when idempotency is desired.
  2. Confirm the item still appears in GET /stories/{story_id} before deleting.
  3. Guard against double-click on the remove button.
  4. Re-fetch the story detail to reconcile item ids.

Example fix

// before
await api.delete(`/stories/${sid}/items/${iid}`);

// after
try { await api.delete(`/stories/${sid}/items/${iid}`); }
catch (e) { if (e.status !== 404) throw e; }
Defensive patterns

Strategy: fallback

Validate before calling

async function itemInStory(sid, iid) {
  const s = await (await fetch(`/api/stories/${encodeURIComponent(sid)}`)).json();
  return Array.isArray(s.items) && s.items.some(i => i.id === iid);
}

Type guard

function isItemId(v) { return typeof v === 'string' && v.trim().length >= 8; }

Try / catch

try { await api.delete(`/stories/${sid}/items/${iid}`); }
catch (e) { if (e.response?.status === 404) return; throw e; }

Prevention

When it happens

Trigger: Removing an item that was already removed, an item_id that does not exist under the given story, or a story_id that does not exist. The composite key (story_id, item_id) does not match a row.

Common situations: Double-remove after optimistic UI update. item_id from a different story. Story deleted while its items list was open.

Related errors


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