{"record":{"id":"b6e18da8e676f8be","repo":"jamiepine/voicebox","slug":"channel-channel-id-not-found","errorCode":null,"errorMessage":"Channel {channel_id} not found","messagePattern":"Channel (.+?) not found","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/services/channels.py","lineNumber":205,"sourceCode":"\nasync def get_channel_voices(channel_id: str, db: Session) -> List[str]:\n    \"\"\"Get list of profile IDs assigned to a channel.\"\"\"\n    mappings = db.query(DBProfileChannelMapping).filter_by(\n        channel_id=channel_id\n    ).all()\n    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    ","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/channels.py#L187-L223","documentation":"Raised as ValueError (translated to HTTP 400 by PUT /channels/{channel_id}/voices) when no DBAudioChannel matches channel_id. set_channel_voices verifies the channel exists before touching any mapping rows. Note the route maps all ValueError to 400, so a missing channel is reported as 400 rather than the more conventional 404 — clients should treat it as 'not found'.","triggerScenarios":"PUT /channels/{channel_id}/voices with a channel_id that doesn't exist (deleted, typo, belongs to another scope). The profile_ids are not yet validated at this point, so this fires first.","commonSituations":"Stale channel_id in the client after deletion; voice-assignment UI opened on a channel that was removed elsewhere; copy/paste id error.","solutions":["GET /channels first and ensure channel_id is present before assigning voices.","On 400 with 'Channel ... not found', invalidate the local channel and refresh.","Upstream, consider routing missing-channel through 404 to disambiguate from a bad profile_ids value (also a 400)."],"exampleFix":"// before\nawait api.setChannelVoices(channelId, { profile_ids });\n\n// after\nconst exists = (await api.listChannels()).some(c => c.id === channelId);\nif (!exists) { refreshChannels(); return; }\nawait api.setChannelVoices(channelId, { profile_ids });","handlingStrategy":"validation","validationCode":"async function assignVoicesToExistingChannel(api, channelId, profileIds) {\n  const channels = await api.listChannels();\n  if (!channels.some(c => c.id === channelId)) {\n    throw new Error('channel not found');\n  }\n  return api.setChannelVoices(channelId, { profile_ids: profileIds });\n}","typeGuard":"function isLiveChannelId(channels, id) {\n  return Array.isArray(channels) && channels.some(c => c.id === id);\n}","tryCatchPattern":"try { await api.setChannelVoices(channelId, { profile_ids }); }\ncatch (e) {\n  // route returns 400 for a missing channel; treat as not-found\n  if (e.status === 400 && /Channel .* not found/.test(e.detail)) {\n    await refreshChannels(); notify('Channel no longer exists');\n  } else throw e;\n}","preventionTips":["GET /channels first; only allow assignment to ids in that list.","Treat a 400 'Channel ... not found' as a removal — refresh and re-render.","Note the route reports missing channels as 400, not 404."],"tags":["api","channels","not-found","fastapi","status-code-mismatch"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}