{"record":{"id":"ed0f73333eeacb89","repo":"jamiepine/voicebox","slug":"channel-with-name-data-name-already-exists","errorCode":null,"errorMessage":"Channel with name '{data.name}' already exists","messagePattern":"Channel with name '(.+?)' already exists","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"backend/services/channels.py","lineNumber":77,"sourceCode":"    \n    return AudioChannelResponse(\n        id=channel.id,\n        name=channel.name,\n        is_default=channel.is_default,\n        device_ids=device_ids,\n        created_at=channel.created_at,\n    )\n\n\nasync def create_channel(\n    data: AudioChannelCreate,\n    db: Session,\n) -> AudioChannelResponse:\n    \"\"\"Create a new audio channel.\"\"\"\n    # Check if name already exists\n    existing = db.query(DBAudioChannel).filter_by(name=data.name).first()\n    if existing:\n        raise ValueError(f\"Channel with name '{data.name}' already exists\")\n    \n    # Create channel\n    channel = DBAudioChannel(\n        id=str(uuid.uuid4()),\n        name=data.name,\n        is_default=False,\n        created_at=datetime.utcnow(),\n    )\n    db.add(channel)\n    db.flush()\n    \n    # Add device mappings\n    for device_id in data.device_ids:\n        mapping = DBChannelDeviceMapping(\n            id=str(uuid.uuid4()),\n            channel_id=channel.id,\n            device_id=device_id,\n        )","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/channels.py#L59-L95","documentation":"Raised as ValueError (HTTP 400) by create_channel when a DBAudioChannel row already exists with the requested name. The check is an exact, case-sensitive filter_by(name=data.name).first() before insert. New channels are always created with is_default=False, so name uniqueness against the default channel is also enforced here.","triggerScenarios":"POST /channels with a name that already names another channel (including the default channel). Two concurrent POSTs racing to create the same name can both pass the check then the second flush/commit fails — though the explicit check usually catches it.","commonSituations":"User types a duplicate channel name; client retries a 'failed' create after the row was actually written; default channel is named 'Default' and the user tries to create 'Default'.","solutions":["GET /channels first and block names already in use (the check is case-sensitive, match exactly).","On 400 with this message, refresh the channel list and let the user pick a new name.","Add a UNIQUE constraint on name at the DB level as a backstop against the race, and map IntegrityError to the same 400."],"exampleFix":"// before\nawait api.createChannel({ name: userInput, device_ids: [] });\n\n// after\nconst taken = (await api.listChannels()).map(c => c.name);\nif (taken.includes(userInput)) {\n  setError('Name already in use'); return;\n}\nawait api.createChannel({ name: userInput, device_ids: [] });","handlingStrategy":"validation","validationCode":"function uniqueChannelName(existing, name) {\n  const taken = new Set(existing.map(c => c.name));\n  return name != null && name.length > 0 && !taken.has(name);\n}\nif (!uniqueChannelName(channels, draft.name)) { setError('Name already in use'); return; }","typeGuard":"function isAvailableChannelName(existing, name) {\n  return typeof name === 'string' && name.trim().length > 0\n    && !existing.some(c => c.name === name);\n}","tryCatchPattern":"try { await api.createChannel(payload); }\ncatch (e) {\n  if (e.status === 400 && /already exists/.test(e.detail)) {\n    await refreshChannels(); notify('Name already in use');\n  } else throw e;\n}","preventionTips":["Fetch /channels and check name availability before submit.","Make the name input case-sensitively unique against existing names.","Add a DB UNIQUE constraint on name as a race-condition backstop."],"tags":["api","channels","validation","duplicate","fastapi"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}