{"record":{"id":"22ee7ac634e8d920","repo":"jamiepine/voicebox","slug":"profile-profile-id-not-found","errorCode":null,"errorMessage":"Profile {profile_id} not found","messagePattern":"Profile (.+?) not found","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/services/channels.py","lineNumber":211,"sourceCode":"    return [m.profile_id for m in mappings]\n\n\nasync def set_channel_voices(\n    channel_id: str,\n    data: ChannelVoiceAssignment,\n    db: Session,\n) -> None:\n    \"\"\"Set which voices are assigned to a channel.\"\"\"\n    # Verify channel exists\n    channel = db.query(DBAudioChannel).filter_by(id=channel_id).first()\n    if not channel:\n        raise ValueError(f\"Channel {channel_id} not found\")\n    \n    # Verify all profiles exist\n    for profile_id in data.profile_ids:\n        profile = db.query(DBVoiceProfile).filter_by(id=profile_id).first()\n        if not profile:\n            raise ValueError(f\"Profile {profile_id} not found\")\n    \n    # Delete existing mappings for this channel\n    db.query(DBProfileChannelMapping).filter_by(channel_id=channel_id).delete()\n    \n    # Add new mappings\n    for profile_id in data.profile_ids:\n        mapping = DBProfileChannelMapping(\n            profile_id=profile_id,\n            channel_id=channel_id,\n        )\n        db.add(mapping)\n    \n    db.commit()\n\n\nasync def get_profile_channels(profile_id: str, db: Session) -> List[str]:\n    \"\"\"Get list of channel IDs assigned to a profile.\"\"\"\n    mappings = db.query(DBProfileChannelMapping).filter_by(","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/channels.py#L193-L229","documentation":"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.","triggerScenarios":"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.","commonSituations":"Profile deleted after the voice-assignment UI was opened (stale checkbox state); bulk-assign pulled ids from an outdated cache; cross-workspace id leak.","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."],"exampleFix":"// before\nawait api.setChannelVoices(channelId, { profile_ids: selectedProfileIds });\n\n// after\nconst live = new Set((await api.listProfiles()).map(p => p.id));\nconst valid = selectedProfileIds.filter(id => live.has(id));\nif (valid.length !== selectedProfileIds.length) {\n  setSelectedProfileIds(valid);  // drop stale\n}\nawait api.setChannelVoices(channelId, { profile_ids: valid });","handlingStrategy":"validation","validationCode":"async function assignOnlyLiveProfiles(api, channelId, profileIds) {\n  const live = new Set((await api.listProfiles()).map(p => p.id));\n  const valid = profileIds.filter(id => live.has(id));\n  if (valid.length !== profileIds.length) {\n    throw new Error('some profiles no longer exist');\n  }\n  return api.setChannelVoices(channelId, { profile_ids: valid });\n}","typeGuard":"function allProfilesLive(liveIds, requested) {\n  const set = new Set(liveIds);\n  return Array.isArray(requested) && requested.every(id => set.has(id));\n}","tryCatchPattern":"try { await api.setChannelVoices(channelId, { profile_ids }); }\ncatch (e) {\n  if (e.status === 400 && /Profile .* not found/.test(e.detail)) {\n    await refreshProfiles(); notify('One or more voices were removed; please reselect');\n  } else throw e;\n}","preventionTips":["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."],"tags":["api","channels","profiles","not-found","validation"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}