usestrix/strix · error · RuntimeError

Scan loop is not ready

Error message

Scan loop is not ready

What it means

Raised by _send_message when the asyncio event loop reserved for the scan (self.scan_loop) is either None or already closed. The controller routes coordinator.send onto that loop via run_coroutine_threadsafe when called from another thread, so a dead loop means there is no executor to deliver the message on. It fires after the coordinator check passed, i.e. the scan infrastructure half-exists.

Source

Thrown at strix/interface/tui/backend/controller.py:365

            raise RuntimeError("No mount confirmation is pending")
        approved = payload.get("approved")
        if not isinstance(approved, bool):
            raise TypeError("approved must be a boolean")
        self.pending_workspace_mount = None
        # Declining skips the mount, it does not abandon the scan. The prompt is
        # the whole of the input either way; the working directory is only an
        # extra the agent may look at, so the run goes ahead without one.
        self.workspace_mount = mount if approved else None
        await self._begin_scan(self._pending_verify)
        return {"approved": approved}

    async def _send_message(self, payload: dict[str, Any]) -> dict[str, Any]:
        agent_id = self._required_string(payload, "agent_id")
        message = self._required_string(payload, "message")
        if self.coordinator is None:
            raise RuntimeError("Agent coordinator is unavailable")
        if self.scan_loop is None or self.scan_loop.is_closed():
            raise RuntimeError("Scan loop is not ready")
        self.live_view.record_user_message(agent_id, message)
        if self.scan_loop is asyncio.get_running_loop():
            delivered = await self.coordinator.send(
                agent_id,
                {"from": "user", "content": message, "type": "instruction"},
            )
        else:
            future = asyncio.run_coroutine_threadsafe(
                self.coordinator.send(
                    agent_id,
                    {"from": "user", "content": message, "type": "instruction"},
                ),
                self.scan_loop,
            )
            delivered = await asyncio.wrap_future(future)
        if not delivered:
            raise RuntimeError("Message could not be delivered")
        return {"sent": True}

View on GitHub (pinned to 8551339130)

Solutions

  1. Check controller.scan_loop and .is_closed() before sending, or gate on scan_state == 'running'
  2. Restart or re-initialize the scan to obtain a fresh loop
  3. Keep the scan loop alive for the whole controller lifetime if post-scan chat must work
  4. In embedded usage, run the controller on a dedicated long-lived loop instead of asyncio.run per operation

Example fix

loop = controller.scan_loop
if loop is None or loop.is_closed():
    show_status("Scan is not running; start a scan to chat with agents")
else:
    await controller._send_message({"agent_id": aid, "message": msg})
Defensive patterns

Strategy: validation

Validate before calling

loop = controller.scan_loop
if loop is None or loop.is_closed():
    show_status("Scan loop is down; message not sent")
    return

Type guard

def loop_alive(loop) -> bool:
    return loop is not None and not loop.is_closed()

Try / catch

try:
    await controller._send_message(payload)
except RuntimeError as e:
    if "Scan loop is not ready" in str(e):
        await restart_scan_or_notify()
    else:
        raise

Prevention

When it happens

Trigger: Sending an agent message after the scan loop was closed (scan finished and loop shut down), or before it was assigned (loop is None) while a coordinator object is present. Also triggered by calling _send_message from a thread after asyncio.run completed.

Common situations: Message sent during scan teardown; scan crashed and the loop was cleaned up while the UI stayed open; embedding the controller in a process where the scan loop runs under asyncio.run and has already returned.

Related errors


AI-assisted analysis of usestrix/strix@8551339130 (2026-08-15). Data as JSON: /api/errors/097baa29644c8945. Report an issue: GitHub.