jamiepine/voicebox · error · HTTPException

Story not found

Error message

Story not found

What it means

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.

Source

Thrown at backend/routes/stories.py:43

    data: models.StoryCreate,
    db: Session = Depends(get_db),
):
    """Create a new story."""
    try:
        return await stories.create_story(data, db)
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))


@router.get("/stories/{story_id}", response_model=models.StoryDetailResponse)
async def get_story(
    story_id: str,
    db: Session = Depends(get_db),
):
    """Get a story with all its items."""
    story = await stories.get_story(story_id, db)
    if not story:
        raise HTTPException(status_code=404, detail="Story not found")
    return story


@router.put("/stories/{story_id}", response_model=models.StoryResponse)
async def update_story(
    story_id: str,
    data: models.StoryCreate,
    db: Session = Depends(get_db),
):
    """Update a story."""
    story = await stories.update_story(story_id, data, db)
    if not story:
        raise HTTPException(status_code=404, detail="Story not found")
    return story


@router.delete("/stories/{story_id}")
async def delete_story(

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Verify story_id against the GET /stories list before deep-linking.
  2. On 404, return the user to the stories list rather than auto-retrying.
  3. Sanitize story_id (trim, validate UUID format) before the request.
  4. Reconcile client-side caches with the server list.

Example fix

// before
window.location = `/stories/${id}`;

// after
if (!await storyExists(id)) redirect('/stories');
window.location = `/stories/${id}`;
Defensive patterns

Strategy: validation

Validate before calling

async function storyExists(id) {
  const r = await fetch(`/api/stories/${encodeURIComponent(id)}`);
  return r.ok;
}

Type guard

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

Try / catch

try { await api.get(`/stories/${id}`); }
catch (e) { if (e.response?.status === 404) redirectToStoriesList(); throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: Stale bookmarked URL. Story deleted in another session. Typo or truncated UUID in the path.

Related errors


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