{"record":{"id":"eff569da82a875c2","repo":"usestrix/strix","slug":"agent-coordinator-is-unavailable","errorCode":null,"errorMessage":"Agent coordinator is unavailable","messagePattern":"Agent coordinator is unavailable","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"strix/interface/tui/backend/controller.py","lineNumber":363,"sourceCode":"        mount = self.pending_workspace_mount\n        if mount is None:\n            raise RuntimeError(\"No mount confirmation is pending\")\n        approved = payload.get(\"approved\")\n        if not isinstance(approved, bool):\n            raise TypeError(\"approved must be a boolean\")\n        self.pending_workspace_mount = None\n        # Declining skips the mount, it does not abandon the scan. The prompt is\n        # the whole of the input either way; the working directory is only an\n        # extra the agent may look at, so the run goes ahead without one.\n        self.workspace_mount = mount if approved else None\n        await self._begin_scan(self._pending_verify)\n        return {\"approved\": approved}\n\n    async def _send_message(self, payload: dict[str, Any]) -> dict[str, Any]:\n        agent_id = self._required_string(payload, \"agent_id\")\n        message = self._required_string(payload, \"message\")\n        if self.coordinator is None:\n            raise RuntimeError(\"Agent coordinator is unavailable\")\n        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:","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/interface/tui/backend/controller.py#L345-L381","documentation":"Raised by _send_message when the controller's coordinator reference is None, meaning no agent coordinator has been constructed yet. The coordinator is created during scan startup (_begin_scan), so this error means a chat message was routed to an agent before any scan exists. It is a state error, not a network error: the handler checks the invariant before attempting any delivery.","triggerScenarios":"Invoking the send-message command with valid agent_id/message strings before a scan has started (coordinator is None), or after the controller was reset and the coordinator torn down. The agent_id and message fields already passed _required_string validation when this fires.","commonSituations":"Frontend fires a chat send during the setup screen; a queued message is dispatched after scan teardown; race between the UI enabling the chat input and the backend creating the coordinator; tests instantiating the controller without starting a scan.","solutions":["Only enable the message input after the scan has started (scan_state == 'running' and coordinator exists)","If the message was sent during startup, retry once the scan is live","Guard the UI action on controller.scan_started / scan_state before dispatching send-message","In tests, start a scan (or inject a fake coordinator) before exercising _send_message"],"exampleFix":"// before\nawait controller._send_message({\"agent_id\": \"planner\", \"message\": \"hi\"})\n\n// after\nif controller.scan_started:\n    await controller._send_message({\"agent_id\": \"planner\", \"message\": \"hi\"})","handlingStrategy":"validation","validationCode":"if controller.coordinator is None or not controller.scan_started:\n    show_status(\"Start a scan before messaging agents\")\n    return","typeGuard":"def scan_ready(c) -> bool:\n    return c.coordinator is not None and c.scan_started and c.scan_state == \"running\"","tryCatchPattern":"try:\n    await controller._send_message(payload)\nexcept RuntimeError as e:\n    if \"coordinator is unavailable\" in str(e):\n        disable_chat_input()  # scan not started yet\n    else:\n        raise","preventionTips":["Gate chat UI on scan_state == 'running'","Disable the message box on the setup screen","Tear down UI handlers when the controller resets so late dispatches cannot occur"],"tags":["tui","state","lifecycle","agent-communication"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}