{"record":{"id":"aa0ec1e2756e3381","repo":"jamiepine/voicebox","slug":"exception-message-from-set-channel-voices-valuee","errorCode":null,"errorMessage":"{exception message from set_channel_voices (ValueError)}","messagePattern":"\\{exception message from set_channel_voices \\(ValueError\\)\\}","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"backend/routes/channels.py","lineNumber":98,"sourceCode":"    try:\n        profile_ids = await channels.get_channel_voices(channel_id, db)\n        return {\"profile_ids\": profile_ids}\n    except ValueError as e:\n        raise HTTPException(status_code=400, detail=str(e))\n\n\n@router.put(\"/channels/{channel_id}/voices\")\nasync def set_channel_voices(\n    channel_id: str,\n    data: models.ChannelVoiceAssignment,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Set which voices are assigned to a channel.\"\"\"\n    try:\n        await channels.set_channel_voices(channel_id, data, db)\n        return {\"message\": \"Channel voices updated successfully\"}\n    except ValueError as e:\n        raise HTTPException(status_code=400, detail=str(e))\n","sourceCodeStart":80,"sourceCodeEnd":99,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/channels.py#L80-L99","documentation":"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.","triggerScenarios":"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.","commonSituations":"Assigning a voice profile that was just deleted; pasting a profile_id from another resource; channel removed between page load and save.","solutions":["GET /channels/{channel_id} to confirm the channel exists before assigning voices.","Validate every profile_id against the current profiles list before submitting.","Parse the detail string: 'Channel ... not found' vs 'Profile ... not found' tells you which side is stale."],"exampleFix":"// before\nawait fetch(`/channels/${cid}/voices`, { method:'PUT', body: JSON.stringify({ profile_ids: picked }) });\n// after\nconst profiles = await fetch('/profiles').then(r => r.json());\nconst valid = picked.filter(id => profiles.some(p => p.id === id));\nif (valid.length !== picked.length) { alert('One or more profiles no longer exist'); return; }\nawait fetch(`/channels/${cid}/voices`, { method:'PUT', body: JSON.stringify({ profile_ids: valid }) });","handlingStrategy":"validation","validationCode":"const [chan, profiles] = await Promise.all([\n  fetch(`/channels/${cid}`).then(r => r.ok),\n  fetch('/profiles').then(r => r.json()),\n]);\nif (!chan) throw new Error('channel missing');\nif (!profileIds.every(id => profiles.some(p => p.id === id))) throw new Error('stale profile id');","typeGuard":"function areValidProfileIds(ids, profiles): ids is string[] {\n  return ids.every(id => profiles.some(p => p.id === id));\n}","tryCatchPattern":"const r = await fetch(`/channels/${cid}/voices`, { method:'PUT', body: JSON.stringify({ profile_ids }) });\nif (r.status === 400) { const { detail } = await r.json(); /* 'Channel .. not found' or 'Profile .. not found' */ }","preventionTips":["Confirm the channel exists before assigning voices.","Filter profile_ids against the current profiles list before submit.","Parse the detail to identify which side is stale."],"tags":["channels","http","validation","not-found","fastapi"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}