jamiepine/voicebox · error · HTTPException

{exception message from set_channel_voices (ValueError)}

Error message

{exception message from set_channel_voices (ValueError)}

What it means

HTTP 400 raised by PUT /channels/{channel_id}/voices when channels.set_channel_voices() raises ValueError. The service raises ValueError for two cases: 'Channel {channel_id} not found' (the channel row does not exist) and 'Profile {profile_id} not found' (one of the supplied profile_ids has no VoiceProfile row). The handler re-wraps the message into 400.

Source

Thrown at backend/routes/channels.py:98

    try:
        profile_ids = await channels.get_channel_voices(channel_id, db)
        return {"profile_ids": profile_ids}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))


@router.put("/channels/{channel_id}/voices")
async def set_channel_voices(
    channel_id: str,
    data: models.ChannelVoiceAssignment,
    db: Session = Depends(get_db),
):
    """Set which voices are assigned to a channel."""
    try:
        await channels.set_channel_voices(channel_id, data, db)
        return {"message": "Channel voices updated successfully"}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

View on GitHub (pinned to 51f49dea19)

Solutions

  1. GET /channels/{channel_id} to confirm the channel exists before assigning voices.
  2. Validate every profile_id against the current profiles list before submitting.
  3. Parse the detail string: 'Channel ... not found' vs 'Profile ... not found' tells you which side is stale.

Example fix

// before
await fetch(`/channels/${cid}/voices`, { method:'PUT', body: JSON.stringify({ profile_ids: picked }) });
// after
const profiles = await fetch('/profiles').then(r => r.json());
const valid = picked.filter(id => profiles.some(p => p.id === id));
if (valid.length !== picked.length) { alert('One or more profiles no longer exist'); return; }
await fetch(`/channels/${cid}/voices`, { method:'PUT', body: JSON.stringify({ profile_ids: valid }) });
Defensive patterns

Strategy: validation

Validate before calling

const [chan, profiles] = await Promise.all([
  fetch(`/channels/${cid}`).then(r => r.ok),
  fetch('/profiles').then(r => r.json()),
]);
if (!chan) throw new Error('channel missing');
if (!profileIds.every(id => profiles.some(p => p.id === id))) throw new Error('stale profile id');

Type guard

function areValidProfileIds(ids, profiles): ids is string[] {
  return ids.every(id => profiles.some(p => p.id === id));
}

Try / catch

const r = await fetch(`/channels/${cid}/voices`, { method:'PUT', body: JSON.stringify({ profile_ids }) });
if (r.status === 400) { const { detail } = await r.json(); /* 'Channel .. not found' or 'Profile .. not found' */ }

Prevention

When it happens

Trigger: PUT /channels/{channel_id}/voices with a channel_id that does not exist, or with a profile_ids array containing a profile ID that does not exist.

Common situations: Assigning a voice profile that was just deleted; pasting a profile_id from another resource; channel removed between page load and save.

Related errors


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