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
- Check controller.scan_loop and .is_closed() before sending, or gate on scan_state == 'running'
- Restart or re-initialize the scan to obtain a fresh loop
- Keep the scan loop alive for the whole controller lifetime if post-scan chat must work
- 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
- Check scan_loop.is_closed() before cross-thread dispatch
- Keep one long-lived loop for the controller lifetime in embedded setups
- Cancel pending UI dispatches during scan teardown
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
- Agent coordinator is unavailable
- Message could not be delivered
- Setup can no longer be changed after the scan starts
- The interactive interface could not start: {exc}
- Unknown command: {command}
AI-assisted analysis of usestrix/strix@8551339130 (2026-08-15).
Data as JSON: /api/errors/097baa29644c8945.
Report an issue: GitHub.