jamiepine/voicebox · error · HTTPException

Story item not found or invalid trim values

Error message

Story item not found or invalid trim values

What it means

Returned by PUT /stories/{story_id}/items/{item_id}/trim when stories.trim_story_item returns None (HTTP 404). The None sentinel is overloaded: it covers both 'item not found' and 'invalid trim values' (e.g., trim points out of range or start >= end), because the service returns the same falsy signal for either condition.

Source

Thrown at backend/routes/stories.py:150

):
    """Move a story item (update position and/or track)."""
    item = await stories.move_story_item(story_id, item_id, data, 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}/trim", response_model=models.StoryItemDetail)
async def trim_story_item(
    story_id: str,
    item_id: str,
    data: models.StoryItemTrim,
    db: Session = Depends(get_db),
):
    """Trim a story item."""
    item = await stories.trim_story_item(story_id, item_id, data, db)
    if item is None:
        raise HTTPException(status_code=404, detail="Story item not found or invalid trim values")
    return item


@router.put("/stories/{story_id}/items/{item_id}/volume", response_model=models.StoryItemDetail)
async def update_story_item_volume(
    story_id: str,
    item_id: str,
    data: models.StoryItemVolumeUpdate,
    db: Session = Depends(get_db),
):
    """Set a story item's per-clip volume (linear gain, 0.0–2.0)."""
    item = await stories.update_story_item_volume(story_id, item_id, data, db)
    if item is None:
        raise HTTPException(status_code=404, detail="Story item not found")
    return item


@router.post("/stories/{story_id}/items/{item_id}/split", response_model=list[models.StoryItemDetail])

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Pre-validate trim values: 0 <= start < end <= item.duration using the current item detail.
  2. Confirm item_id exists in the story before trimming.
  3. Refresh item detail after prior trims to get updated bounds.
  4. Clamp trim markers to [0, duration] and ensure start < end client-side.

Example fix

// before
await api.put(`/stories/${sid}/items/${iid}/trim`, { start, end });

// after
const item = await getItem(sid, iid);
if (!item) throw new NotFound();
const s = clamp(start, 0, item.duration);
const e = clamp(end, 0, item.duration);
if (s >= e) throw new UserError('start must be < end');
await api.put(`/stories/${sid}/items/${iid}/trim`, { start: s, end: e });
Defensive patterns

Strategy: validation

Validate before calling

function validateTrim(item, start, end) {
  if (!item) throw new Error('item not found');
  const s = Math.max(0, Math.min(start, item.duration));
  const e = Math.max(0, Math.min(end, item.duration));
  if (s >= e) throw new Error('start must be < end');
  return { start: s, end: e };
}

Type guard

function isTrimPayload(v) { return !!v && typeof v.start === 'number' && typeof v.end === 'number'; }

Try / catch

try { await api.put(`/stories/${sid}/items/${iid}/trim`, payload); }
catch (e) { if (e.response?.status === 404) { await refreshItem(sid, iid); showUser('item missing or trim invalid'); } throw e; }

Prevention

When it happens

Trigger: Trimming a non-existent item, or supplying trim start/end values that are negative, exceed clip duration, or where start >= end. After a prior trim, the item's effective bounds may have changed, making the new values invalid.

Common situations: Editor computes trim points against stale clip duration. item_id stale after split. Trim UI allowing start >= end or out-of-bounds markers.

Related errors


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