jamiepine/voicebox · error · HTTPException

Story or generation not found

Error message

Story or generation not found

What it means

Returned by POST /stories/{story_id}/items when stories.add_item_to_story returns falsy (HTTP 404). The single guard covers two distinct miss conditions: the story_id does not exist, or the generation referenced in StoryItemCreate does not exist. The message combines both because the service returns one falsy signal for either.

Source

Thrown at backend/routes/stories.py:81

    db: Session = Depends(get_db),
):
    """Delete a story."""
    success = await stories.delete_story(story_id, db)
    if not success:
        raise HTTPException(status_code=404, detail="Story not found")
    return {"message": "Story deleted successfully"}


@router.post("/stories/{story_id}/items", response_model=models.StoryItemDetail)
async def add_story_item(
    story_id: str,
    data: models.StoryItemCreate,
    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(

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Verify both story_id (GET /stories/{story_id}) and the referenced generation exist before adding.
  2. Refresh the generations list to obtain a current generation_id.
  3. If the message is ambiguous in the UI, pre-check both entities to disambiguate.
  4. Avoid holding generation ids long-term; re-fetch before use.

Example fix

// before
await api.post(`/stories/${sid}/items`, { generation_id: gen });

// after
if (!await storyExists(sid) || !await generationExists(gen))
  throw new NotFoundError('story or generation');
await api.post(`/stories/${sid}/items`, { generation_id: gen });
Defensive patterns

Strategy: validation

Validate before calling

async function canAddItem(storyId, generationId) {
  const [s, g] = await Promise.all([
    fetch(`/api/stories/${encodeURIComponent(storyId)}`),
    fetch(`/api/generations/${encodeURIComponent(generationId)}`),
  ]);
  return s.ok && g.ok;
}

Type guard

function isItemCreate(v) { return !!v && typeof v.generation_id === 'string'; }

Try / catch

try { await api.post(`/stories/${sid}/items`, payload); }
catch (e) { if (e.response?.status === 404) disambiguateStoryOrGeneration(sid, payload.generation_id); throw e; }

Prevention

When it happens

Trigger: Adding a generation to a non-existent story, or referencing a generation_id that does not exist (wrong id, deleted, or belongs to another scope). The handler cannot distinguish which from the return value alone.

Common situations: Generation was deleted between creation and adding to a story. Story_id from a stale list. Generation_id copied from a different environment.

Related errors


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