{"record":{"id":"6730e4ae448c64e3","repo":"jamiepine/voicebox","slug":"exception-message-from-delete-channel-valueerror","errorCode":null,"errorMessage":"{exception message from delete_channel (ValueError)}","messagePattern":"\\{exception message from delete_channel \\(ValueError\\)\\}","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"backend/routes/channels.py","lineNumber":71,"sourceCode":"            raise HTTPException(status_code=404, detail=\"Channel not found\")\n        return channel\n    except ValueError as e:\n        raise HTTPException(status_code=400, detail=str(e))\n\n\n@router.delete(\"/channels/{channel_id}\")\nasync def delete_channel(\n    channel_id: str,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Delete an audio channel.\"\"\"\n    try:\n        success = await channels.delete_channel(channel_id, db)\n        if not success:\n            raise HTTPException(status_code=404, detail=\"Channel not found\")\n        return {\"message\": \"Channel deleted successfully\"}\n    except ValueError as e:\n        raise HTTPException(status_code=400, detail=str(e))\n\n\n@router.get(\"/channels/{channel_id}/voices\")\nasync def get_channel_voices(\n    channel_id: str,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Get list of profile IDs assigned to a channel.\"\"\"\n    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,","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/channels.py#L53-L89","documentation":"HTTP 400 raised by DELETE /channels/{channel_id} when channels.delete_channel() raises ValueError. The service raises exactly one ValueError here: 'Cannot delete the default channel', fired when the targeted row has is_default=true. The handler re-wraps the message into a 400. The default channel is protected and cannot be removed through this endpoint.","triggerScenarios":"Issuing DELETE against the channel whose is_default flag is true (the seeded/primary channel).","commonSituations":"User selects the system default channel in a delete UI; automation enumerates all channels and tries to delete each; confusing the default channel with a user-created one.","solutions":["Do not expose a delete action for channels where is_default is true.","If you need fewer channels, delete user-created ones or repoint devices off the default first.","Surface the detail verbatim so the user understands it is a protected resource."],"exampleFix":"// before\nawait fetch(`/channels/${id}`, { method: 'DELETE' });\n// after\nif (channel.is_default) { alert('The default channel cannot be deleted'); return; }\nawait fetch(`/channels/${id}`, { method: 'DELETE' });","handlingStrategy":"validation","validationCode":"if (channel.is_default) throw new Error('default channel is protected from deletion');","typeGuard":"function isProtectedChannel(c): c is { is_default: true } { return c?.is_default === true; }","tryCatchPattern":"const r = await fetch(`/channels/${id}`, { method: 'DELETE' });\nif (r.status === 400) { const { detail } = await r.json(); /* 'Cannot delete the default channel' */ }","preventionTips":["Hide or disable delete for channels where is_default is true.","If you must reduce channels, delete user-created ones.","Tell the user the default channel is intentional and protected."],"tags":["channels","http","validation","conflict","fastapi"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}