{"record":{"id":"fe8776dd098f03ce","repo":"jamiepine/voicebox","slug":"story-not-found","errorCode":null,"errorMessage":"Story not found","messagePattern":"Story not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"backend/routes/stories.py","lineNumber":43,"sourceCode":"    data: models.StoryCreate,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Create a new story.\"\"\"\n    try:\n        return await stories.create_story(data, db)\n    except Exception as e:\n        raise HTTPException(status_code=400, detail=str(e))\n\n\n@router.get(\"/stories/{story_id}\", response_model=models.StoryDetailResponse)\nasync def get_story(\n    story_id: str,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Get a story with all its items.\"\"\"\n    story = await stories.get_story(story_id, db)\n    if not story:\n        raise HTTPException(status_code=404, detail=\"Story not found\")\n    return story\n\n\n@router.put(\"/stories/{story_id}\", response_model=models.StoryResponse)\nasync def update_story(\n    story_id: str,\n    data: models.StoryCreate,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Update a story.\"\"\"\n    story = await stories.update_story(story_id, data, db)\n    if not story:\n        raise HTTPException(status_code=404, detail=\"Story not found\")\n    return story\n\n\n@router.delete(\"/stories/{story_id}\")\nasync def delete_story(","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/stories.py#L25-L61","documentation":"Returned by GET /stories/{story_id} when stories.get_story returns a falsy value (HTTP 404). The lookup runs first; the detail response is only built if a story is found. It is a straightforward entity-miss on the path parameter.","triggerScenarios":"GET /stories/{story_id} where story_id is unknown, deleted, or belongs to another scope. Reading a story that was removed between list and detail fetch.","commonSituations":"Stale bookmarked URL. Story deleted in another session. Typo or truncated UUID in the path.","solutions":["Verify story_id against the GET /stories list before deep-linking.","On 404, return the user to the stories list rather than auto-retrying.","Sanitize story_id (trim, validate UUID format) before the request.","Reconcile client-side caches with the server list."],"exampleFix":"// before\nwindow.location = `/stories/${id}`;\n\n// after\nif (!await storyExists(id)) redirect('/stories');\nwindow.location = `/stories/${id}`;","handlingStrategy":"validation","validationCode":"async function storyExists(id) {\n  const r = await fetch(`/api/stories/${encodeURIComponent(id)}`);\n  return r.ok;\n}","typeGuard":"function isStoryId(v) { return typeof v === 'string' && v.trim().length >= 8; }","tryCatchPattern":"try { await api.get(`/stories/${id}`); }\ncatch (e) { if (e.response?.status === 404) redirectToStoriesList(); throw e; }","preventionTips":["Validate story id format and existence before deep-linking.","Reconcile bookmarked URLs against the current list.","Return to the list rather than retrying on 404."],"tags":["fastapi","http","not-found","stories","rest"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}