{"record":{"id":"18e4ab97a20dd349","repo":"jamiepine/voicebox","slug":"story-item-not-found","errorCode":null,"errorMessage":"Story item not found","messagePattern":"Story item not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"backend/routes/stories.py","lineNumber":94,"sourceCode":"    db: Session = Depends(get_db),\n):\n    \"\"\"Add a generation to a story.\"\"\"\n    item = await stories.add_item_to_story(story_id, data, db)\n    if not item:\n        raise HTTPException(status_code=404, detail=\"Story or generation not found\")\n    return item\n\n\n@router.delete(\"/stories/{story_id}/items/{item_id}\")\nasync def remove_story_item(\n    story_id: str,\n    item_id: str,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Remove a story item from a story.\"\"\"\n    success = await stories.remove_item_from_story(story_id, item_id, db)\n    if not success:\n        raise HTTPException(status_code=404, detail=\"Story item not found\")\n    return {\"message\": \"Item removed successfully\"}\n\n\n@router.put(\"/stories/{story_id}/items/times\")\nasync def update_story_item_times(\n    story_id: str,\n    data: models.StoryItemBatchUpdate,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Update story item timecodes.\"\"\"\n    success = await stories.update_story_item_times(story_id, data, db)\n    if not success:\n        raise HTTPException(status_code=400, detail=\"Invalid timecode update request\")\n    return {\"message\": \"Item timecodes updated successfully\"}\n\n\n@router.put(\"/stories/{story_id}/items/reorder\", response_model=list[models.StoryItemDetail])\nasync def reorder_story_items(","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/stories.py#L76-L112","documentation":"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.","triggerScenarios":"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.","commonSituations":"Double-remove after optimistic UI update. item_id from a different story. Story deleted while its items list was open.","solutions":["Treat 404 on item removal as 'already gone' success when idempotency is desired.","Confirm the item still appears in GET /stories/{story_id} before deleting.","Guard against double-click on the remove button.","Re-fetch the story detail to reconcile item ids."],"exampleFix":"// before\nawait api.delete(`/stories/${sid}/items/${iid}`);\n\n// after\ntry { await api.delete(`/stories/${sid}/items/${iid}`); }\ncatch (e) { if (e.status !== 404) throw e; }","handlingStrategy":"fallback","validationCode":"async function itemInStory(sid, iid) {\n  const s = await (await fetch(`/api/stories/${encodeURIComponent(sid)}`)).json();\n  return Array.isArray(s.items) && s.items.some(i => i.id === iid);\n}","typeGuard":"function isItemId(v) { return typeof v === 'string' && v.trim().length >= 8; }","tryCatchPattern":"try { await api.delete(`/stories/${sid}/items/${iid}`); }\ncatch (e) { if (e.response?.status === 404) return; throw e; }","preventionTips":["Treat 404 on item removal as 'already gone'.","Guard against double-click on the remove button.","Re-fetch story detail to reconcile item ids."],"tags":["fastapi","http","not-found","stories","items","rest","idempotent"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}