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
- Refresh the voice profile list and recompute profile_ids immediately before assigning.
- Filter out any ids not in the current profile set before sending.
- 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
- Refresh the profile list immediately before assigning voices.
- Filter selected ids through the live profile set.
- Drop stale profile ids from selection state after a profile delete.
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
- {exception message from set_channel_voices (ValueError)}
- Story item or version not found
- Channel with name '{data.name}' already exists
- Cannot modify the default channel
- Cannot delete the default channel
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/22ee7ac634e8d920.
Report an issue: GitHub.