{"record":{"id":"b962a6925a82c742","repo":"usestrix/strix","slug":"message-could-not-be-delivered","errorCode":null,"errorMessage":"Message could not be delivered","messagePattern":"Message could not be delivered","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"strix/interface/tui/backend/controller.py","lineNumber":382,"sourceCode":"        if self.scan_loop is None or self.scan_loop.is_closed():\n            raise RuntimeError(\"Scan loop is not ready\")\n        self.live_view.record_user_message(agent_id, message)\n        if self.scan_loop is asyncio.get_running_loop():\n            delivered = await self.coordinator.send(\n                agent_id,\n                {\"from\": \"user\", \"content\": message, \"type\": \"instruction\"},\n            )\n        else:\n            future = asyncio.run_coroutine_threadsafe(\n                self.coordinator.send(\n                    agent_id,\n                    {\"from\": \"user\", \"content\": message, \"type\": \"instruction\"},\n                ),\n                self.scan_loop,\n            )\n            delivered = await asyncio.wrap_future(future)\n        if not delivered:\n            raise RuntimeError(\"Message could not be delivered\")\n        return {\"sent\": True}\n\n    async def _stop_agent(self, payload: dict[str, Any]) -> dict[str, Any]:\n        agent_id = self._required_string(payload, \"agent_id\")\n        agent = self.live_view.agents.get(agent_id)\n        if agent is None:\n            raise ValueError(f\"Unknown agent: {agent_id}\")\n        status = str(agent.get(\"status\", \"\"))\n        if status not in _STOPPABLE_AGENT_STATUSES:\n            raise RuntimeError(f\"Agent '{agent_id}' cannot be stopped while {status or 'unknown'}\")\n        if self.coordinator is None or self.scan_loop is None or self.scan_loop.is_closed():\n            raise RuntimeError(\"Scan loop is not ready\")\n        if self.scan_loop is asyncio.get_running_loop():\n            accepted = await self.coordinator.cancel_descendants_graceful(agent_id)\n        else:\n            future = asyncio.run_coroutine_threadsafe(\n                self.coordinator.cancel_descendants_graceful(agent_id), self.scan_loop\n            )","sourceCodeStart":364,"sourceCodeEnd":400,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/interface/tui/backend/controller.py#L364-L400","documentation":"Raised by _send_message after coordinator.send completed but returned a falsy 'delivered' result. The coordinator's send API returns a boolean indicating whether the target agent actually received the message; false means the agent id was accepted at the controller level but no live mailbox took the message (agent finished, was cancelled, or its inbox closed between the UI lookup and delivery).","triggerScenarios":"Sending a message to an agent that completed or was stopped concurrently — the live_view still lists it, but coordinator.send returns False. Also occurs when the agent id is stale after a scan restart while old UI state persists.","commonSituations":"User types a message while the target agent is finishing its task; race between the agents panel render and the agent lifecycle; UI showing cached agent list after scan restart.","solutions":["Refresh the agent list (live_view.agents) and resend to an agent with an active status","Retry once — transient races during agent handoff usually clear","Surface the failure to the UI so the user knows the message was not delivered","If it persists for one agent id, that agent is gone; pick another agent or restart the scan"],"exampleFix":"# before\nresult = await controller._send_message({\"agent_id\": aid, \"message\": msg})\n\n# after\ntry:\n    result = await controller._send_message({\"agent_id\": aid, \"message\": msg})\nexcept RuntimeError:\n    # agent vanished mid-delivery; refresh and retry once with a live agent\n    agents = controller.live_view.agents\n    aid = next((a for a, m in agents.items() if m.get(\"status\") == \"running\"), None)\n    if aid:\n        result = await controller._send_message({\"agent_id\": aid, \"message\": msg})","handlingStrategy":"retry","validationCode":"agents = controller.live_view.agents\nif agent_id not in agents or agents[agent_id].get(\"status\") not in (\"running\", \"waiting\", \"budget_paused\"):\n    agent_id = pick_active_agent(agents)  # refresh before sending","typeGuard":"def agent_deliverable(agents: dict, aid: str) -> bool:\n    m = agents.get(aid)\n    return bool(m) and m.get(\"status\") in {\"running\", \"waiting\", \"budget_paused\"}","tryCatchPattern":"try:\n    await controller._send_message({\"agent_id\": aid, \"message\": msg})\nexcept RuntimeError as e:\n    if \"could not be delivered\" in str(e):\n        aid = pick_active_agent(controller.live_view.agents)\n        if aid:\n            await controller._send_message({\"agent_id\": aid, \"message\": msg})\n    else:\n        raise","preventionTips":["Always pick the agent id from the latest live_view snapshot","Retry once on delivery failure — races during agent handoff are transient","Show delivery failures in the UI instead of silently dropping messages"],"tags":["tui","agent-communication","race-condition","delivery"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}