usestrix/strix · error · RuntimeError

Message could not be delivered

Error message

Message could not be delivered

What it means

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).

Source

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

        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}

    async def _stop_agent(self, payload: dict[str, Any]) -> dict[str, Any]:
        agent_id = self._required_string(payload, "agent_id")
        agent = self.live_view.agents.get(agent_id)
        if agent is None:
            raise ValueError(f"Unknown agent: {agent_id}")
        status = str(agent.get("status", ""))
        if status not in _STOPPABLE_AGENT_STATUSES:
            raise RuntimeError(f"Agent '{agent_id}' cannot be stopped while {status or 'unknown'}")
        if self.coordinator is None or self.scan_loop is None or self.scan_loop.is_closed():
            raise RuntimeError("Scan loop is not ready")
        if self.scan_loop is asyncio.get_running_loop():
            accepted = await self.coordinator.cancel_descendants_graceful(agent_id)
        else:
            future = asyncio.run_coroutine_threadsafe(
                self.coordinator.cancel_descendants_graceful(agent_id), self.scan_loop
            )

View on GitHub (pinned to 8551339130)

Solutions

  1. Refresh the agent list (live_view.agents) and resend to an agent with an active status
  2. Retry once — transient races during agent handoff usually clear
  3. Surface the failure to the UI so the user knows the message was not delivered
  4. If it persists for one agent id, that agent is gone; pick another agent or restart the scan

Example fix

# before
result = await controller._send_message({"agent_id": aid, "message": msg})

# after
try:
    result = await controller._send_message({"agent_id": aid, "message": msg})
except RuntimeError:
    # agent vanished mid-delivery; refresh and retry once with a live agent
    agents = controller.live_view.agents
    aid = next((a for a, m in agents.items() if m.get("status") == "running"), None)
    if aid:
        result = await controller._send_message({"agent_id": aid, "message": msg})
Defensive patterns

Strategy: retry

Validate before calling

agents = controller.live_view.agents
if agent_id not in agents or agents[agent_id].get("status") not in ("running", "waiting", "budget_paused"):
    agent_id = pick_active_agent(agents)  # refresh before sending

Type guard

def agent_deliverable(agents: dict, aid: str) -> bool:
    m = agents.get(aid)
    return bool(m) and m.get("status") in {"running", "waiting", "budget_paused"}

Try / catch

try:
    await controller._send_message({"agent_id": aid, "message": msg})
except RuntimeError as e:
    if "could not be delivered" in str(e):
        aid = pick_active_agent(controller.live_view.agents)
        if aid:
            await controller._send_message({"agent_id": aid, "message": msg})
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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