jamiepine/voicebox · error · ValueError

Profile {profile_id} not found

Error message

Profile {profile_id} not found

What it means

Raised as ValueError (HTTP 400 by PUT /channels/{channel_id}/voices) during set_channel_voices when one of data.profile_ids does not resolve to a DBVoiceProfile. The channel has already been verified to exist (error 216 fires first if not); profiles are checked one at a time in order, so the message names the first missing profile_id encountered.

Source

Thrown at backend/services/channels.py:211

    return [m.profile_id for m in mappings]


async def set_channel_voices(
    channel_id: str,
    data: ChannelVoiceAssignment,
    db: Session,
) -> None:
    """Set which voices are assigned to a channel."""
    # Verify channel exists
    channel = db.query(DBAudioChannel).filter_by(id=channel_id).first()
    if not channel:
        raise ValueError(f"Channel {channel_id} not found")
    
    # Verify all profiles exist
    for profile_id in data.profile_ids:
        profile = db.query(DBVoiceProfile).filter_by(id=profile_id).first()
        if not profile:
            raise ValueError(f"Profile {profile_id} not found")
    
    # Delete existing mappings for this channel
    db.query(DBProfileChannelMapping).filter_by(channel_id=channel_id).delete()
    
    # Add new mappings
    for profile_id in data.profile_ids:
        mapping = DBProfileChannelMapping(
            profile_id=profile_id,
            channel_id=channel_id,
        )
        db.add(mapping)
    
    db.commit()


async def get_profile_channels(profile_id: str, db: Session) -> List[str]:
    """Get list of channel IDs assigned to a profile."""
    mappings = db.query(DBProfileChannelMapping).filter_by(

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Refresh the voice profile list and recompute profile_ids immediately before assigning.
  2. Filter out any ids not in the current profile set before sending.
  3. On 400 'Profile ... not found', reload profiles and re-render the assignment UI.

Example fix

// before
await api.setChannelVoices(channelId, { profile_ids: selectedProfileIds });

// after
const live = new Set((await api.listProfiles()).map(p => p.id));
const valid = selectedProfileIds.filter(id => live.has(id));
if (valid.length !== selectedProfileIds.length) {
  setSelectedProfileIds(valid);  // drop stale
}
await api.setChannelVoices(channelId, { profile_ids: valid });
Defensive patterns

Strategy: validation

Validate before calling

async function assignOnlyLiveProfiles(api, channelId, profileIds) {
  const live = new Set((await api.listProfiles()).map(p => p.id));
  const valid = profileIds.filter(id => live.has(id));
  if (valid.length !== profileIds.length) {
    throw new Error('some profiles no longer exist');
  }
  return api.setChannelVoices(channelId, { profile_ids: valid });
}

Type guard

function allProfilesLive(liveIds, requested) {
  const set = new Set(liveIds);
  return Array.isArray(requested) && requested.every(id => set.has(id));
}

Try / catch

try { await api.setChannelVoices(channelId, { profile_ids }); }
catch (e) {
  if (e.status === 400 && /Profile .* not found/.test(e.detail)) {
    await refreshProfiles(); notify('One or more voices were removed; please reselect');
  } else throw e;
}

Prevention

When it happens

Trigger: Assigning voice profiles to a channel using profile_ids that include a deleted profile, a typo'd id, or an id from another workspace. The check loops every supplied profile_id and fails on the first miss.

Common situations: Profile deleted after the voice-assignment UI was opened (stale checkbox state); bulk-assign pulled ids from an outdated cache; cross-workspace id leak.

Related errors


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