{"record":{"id":"e354a6375bea0f14","repo":"agentscope-ai/agentscope","slug":"channel-channel-id-not-found","errorCode":null,"errorMessage":"Channel '{channel_id}' not found.","messagePattern":"Channel '(.+?)' not found\\.","errorType":"error_code","errorClass":"ChannelError","httpStatus":404,"severity":"error","filePath":"src/agentscope/app/_service/_channel.py","lineNumber":209,"sourceCode":"        fields = await self._bus.registry_getall(\n            MessageBusKeys.channel_seen_chats(channel_id),\n        )\n        return sorted(fields.keys())\n\n    # -- internals --\n\n    async def _require(self, channel_id: str) -> ChannelRecord:\n        \"\"\"Load a channel record or raise a 404 ``ChannelError``.\n\n        Args:\n            channel_id (`str`): The channel to load.\n\n        Returns:\n            `ChannelRecord`: The record.\n        \"\"\"\n        record = await self._storage.get_channel(channel_id)\n        if record is None:\n            raise ChannelError(f\"Channel '{channel_id}' not found.\", 404)\n        return record\n\n    async def _notify(self, channel_id: str) -> None:\n        \"\"\"Publish a lifecycle notification (best-effort).\n\n        Args:\n            channel_id (`str`): The changed channel; reconcile re-reads\n                storage, so the payload is only a nudge.\n        \"\"\"\n        try:\n            await self._bus.publish(\n                MessageBusKeys.channel_lifecycle(),\n                {\"channel_id\": channel_id},\n            )\n        except Exception:  # pylint: disable=broad-except\n            # Lost notifications are recovered by the periodic reconcile.\n            pass\n","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/app/_service/_channel.py#L191-L227","documentation":"Thrown by ChannelService._require when storage.get_channel returns None for the given channel_id; it is the shared guard used by update and delete. It signals the channel id does not exist (never created or already removed) and surfaces as ChannelError with HTTP 404.","triggerScenarios":"Calling update(channel_id, ...) or delete(channel_id) with an id that was never registered, was deleted earlier, or came from a stale config/UI list.","commonSituations":"Deleting a channel twice (double click or retry after the first request succeeded); config files or environment variables holding a channel id from a previous environment; races where another admin removed the channel concurrently.","solutions":["Verify the channel still exists (list channels) before update/delete","Treat 404 ChannelError in delete flows as already-deleted success when idempotency matters","Refresh channel ids from the current environment instead of hardcoding them in configs","Guard against double-submits in the UI by disabling the button after the first request"],"exampleFix":"# before\nawait channel_service.delete(channel_id=\"ch-old\")  # 404\n# after\nfrom ... import ChannelError\ntry:\n    await channel_service.delete(channel_id=\"ch-old\")\nexcept ChannelError as e:\n    if e.status_code != 404:\n        raise  # already gone; treat as success","handlingStrategy":"try-catch","validationCode":"record = await channel_client.get_channel(channel_id)\nif record is None:\n    raise ValueError(f\"Channel {channel_id} does not exist\")\nawait channel_client.update(channel_id, ...)","typeGuard":null,"tryCatchPattern":"try:\n    await channel_service.delete(channel_id)\nexcept ChannelError as e:\n    if e.status_code == 404:\n        return  # already deleted; idempotent success\n    raise","preventionTips":["Re-fetch channel lists before acting on ids cached in configs","Treat delete-404 as success in idempotent workflows"],"tags":["channel","not-found-404","lifecycle"],"backgroundTag":"resource-not-found-404","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}