usestrix/strix · error · RuntimeError

Scan start is unavailable

Error message

Scan start is unavailable

What it means

Raised by _start (strix/interface/tui/backend/controller.py:315) when the controller was constructed without an _on_start callback. The callback is the wiring that actually launches the scan; without it the TUI shell can collect setup input but cannot begin a run, so pressing start raises RuntimeError. This is an internal wiring/dependency-injection defect, not a user input error.

Source

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

    async def _start(self, payload: dict[str, Any]) -> dict[str, Any]:
        if self.scan_started or self._start_in_progress:
            raise RuntimeError("Scan is already starting or running")
        # A bare prompt launches optimistically, like a coding agent: it skips
        # the network model preflight and surfaces any model error live. A named
        # target keeps the preflight so a real scan does not commit blind.
        verify = payload.get("verify", True)
        if not isinstance(verify, bool):
            raise TypeError("verify must be a boolean")
        # Launching with no target mounts the working directory, so it requires
        # the user's explicit confirmation rather than happening silently.
        mount_working_dir = payload.get("mount_working_dir", False)
        if not isinstance(mount_working_dir, bool):
            raise TypeError("mount_working_dir must be a boolean")
        model = (load_settings().llm.model or "").strip()
        if not model:
            raise ValueError("No model configured. Set STRIX_LLM first.")
        if self._on_start is None:
            raise RuntimeError("Scan start is unavailable")
        if not self.targets:
            if not mount_working_dir:
                raise ValueError("No target set. Add a target first.")
            # Mounting the working directory needs the user's confirmation, and
            # that is asked in the live view. Enter it now and prepare nothing
            # until the answer arrives, so declining leaves no run behind.
            self.pending_workspace_mount = str(Path.cwd())
            self._pending_verify = verify
            self.setup_mode = False
            self.scan_started = True
            self.scan_state = "preparing"
            return {"started": True}
        await self._begin_scan(verify)
        return {"started": True}

    async def _begin_scan(self, verify: bool) -> None:
        if self._on_start is None:
            raise RuntimeError("Scan start is unavailable")

View on GitHub (pinned to 8551339130)

Solutions

  1. Pass an async on_start callback when constructing the controller (see how the production TUI runtime wires it).
  2. In tests, inject a stub: controller = Controller(on_start=fake_start) before sending setup.start.
  3. Check the constructor signature for your Strix version — the parameter may have been renamed/added.

Example fix

# before
controller = SetupController()              # no on_start
await controller.handle("setup.start", {})   # RuntimeError: Scan start is unavailable

# after
async def fake_start(verify: bool) -> None: ...
controller = SetupController(on_start=fake_start)
await controller.handle("setup.start", {})
Defensive patterns

Strategy: type-guard

Validate before calling

if getattr(controller, '_on_start', None) is None:
    raise RuntimeError('controller has no on_start; wire the launcher before use')

Type guard

def controller_can_start(controller) -> bool:
    return getattr(controller, '_on_start', None) is not None

Try / catch

try:
    await controller.handle("setup.start", {})
except RuntimeError as exc:
    if 'Scan start is unavailable' in str(exc):
        raise RuntimeError('embedding bug: construct controller with on_start=...') from exc
    raise

Prevention

When it happens

Trigger: Instantiating SetupController (or equivalent) directly in tests or embedding code without passing the on_start coroutine; a TUI composition refactor that forgot to inject the launcher; version skew where the constructor signature changed.

Common situations: Writing automated tests for the controller without a fake on_start; embedding the backend controller in a custom host app; partial upgrades where an old caller no longer supplies the callback.

Related errors


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