jamiepine/voicebox · error · HTTPException

Story item or version not found

Error message

Story item or version not found

What it means

Raised (HTTP 404) by PUT /stories/{story_id}/items/{item_id}/version when stories.set_story_item_version returns None. The service returns None when the item is missing, its generation is missing, or the supplied version_id does not belong to the item's generation_id (DBGenerationVersion lookup filtered by both id and generation_id fails). The message covers all three cases.

Source

Thrown at backend/routes/stories.py:205

):
    """Duplicate a story item."""
    item = await stories.duplicate_story_item(story_id, item_id, db)
    if item is None:
        raise HTTPException(status_code=404, detail="Story item not found")
    return item


@router.put("/stories/{story_id}/items/{item_id}/version", response_model=models.StoryItemDetail)
async def set_story_item_version(
    story_id: str,
    item_id: str,
    data: models.StoryItemVersionUpdate,
    db: Session = Depends(get_db),
):
    """Pin a story item to a specific generation version."""
    item = await stories.set_story_item_version(story_id, item_id, data, db)
    if item is None:
        raise HTTPException(status_code=404, detail="Story item or version not found")
    return item


@router.get("/stories/{story_id}/export-audio")
async def export_story_audio(
    story_id: str,
    db: Session = Depends(get_db),
):
    """Export story as single mixed audio file."""
    try:
        story = db.query(database.Story).filter_by(id=story_id).first()
        if not story:
            raise HTTPException(status_code=404, detail="Story not found")

        audio_bytes = await stories.export_story_audio(story_id, db)
        if not audio_bytes:
            raise HTTPException(status_code=400, detail="Story has no audio items")

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Re-fetch the available versions for the item's current generation_id before pinning.
  2. To unpin, send version_id = null/None — that path skips the version lookup and only needs a valid item.
  3. Verify item_id belongs to story_id (GET /stories/{story_id}) if the 404 is unexpected.

Example fix

// before
await api.setItemVersion(storyId, itemId, { version_id: cachedVersionId });

// after
const versions = await api.listGenerationVersions(item.generation_id);
if (!versions.some(v => v.id === cachedVersionId)) {
  throw new Error('version no longer available for this generation');
}
await api.setItemVersion(storyId, itemId, { version_id: cachedVersionId });
Defensive patterns

Strategy: validation

Validate before calling

async function pinVersionIfAvailable(api, storyId, itemId, generationId, versionId) {
  if (versionId == null) {
    return api.setItemVersion(storyId, itemId, { version_id: null }); // unpin always allowed
  }
  const versions = await api.listGenerationVersions(generationId);
  if (!versions.some(v => v.id === versionId)) {
    throw new Error('version not available for this generation');
  }
  return api.setItemVersion(storyId, itemId, { version_id: versionId });
}

Type guard

function isPinnableVersion(versions, versionId, item) {
  return Array.isArray(versions)
    && versions.some(v => v.id === versionId && v.generation_id === item.generation_id);
}

Try / catch

try { await api.setItemVersion(storyId, itemId, payload); }
catch (e) {
  if (e.status === 404) { await refreshItem(storyId, itemId); notify('Item or version not available'); }
  else throw e;
}

Prevention

When it happens

Trigger: PUT .../version with a version_id from a different generation, a version_id that was deleted, an item_id/story_id mismatch, or pinning a version after the generation's version rows were regenerated.

Common situations: UI lists version options from one generation but the item was since re-pointed to another generation; client caches version_id across a regeneration that dropped old versions.

Related errors


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