github/copilot-sdk · error · RuntimeError

Client is not connected. Call start() first.

Error message

Client is not connected. Call start() first.

What it means

This RuntimeError is thrown by the `rpc` property of CopilotClient when the internal `_rpc` handle has not been initialized. The client only creates `_rpc` during `start()` (or `connect()`), so accessing server-scoped RPC methods before the transport is up is an invalid state. The library throws early instead of returning None to prevent confusing downstream attribute errors.

Solutions

  1. Call `await client.start()` before accessing `client.rpc`.
  2. Check `client._state` / wrap usage in `async with CopilotClient(...) as client:` so lifecycle is managed automatically.
  3. If the client was stopped, create a new CopilotClient instance instead of reusing it.
  4. Inspect logs from the failed start() if start() was called but the state is 'error'.

Example fix

// before
client = CopilotClient(...)
result = await client.rpc.listModels()
// after
client = CopilotClient(...)
await client.start()
result = await client.rpc.listModels()
Defensive patterns

Strategy: try-catch

Validate before calling

if not hasattr(client, "rpc") or client._rpc is None:
    await client.start()

Type guard

def is_connected(client) -> bool:
    return getattr(client, "_rpc", None) is not None

Try / catch

try:
    rpc = client.rpc
except RuntimeError as e:
    if "not connected" in str(e):
        await client.start()
        rpc = client.rpc
    else:
        raise

Prevention

When it happens

Trigger: Accessing `client.rpc` (or any property built on it) before calling `await client.start()`, after `stop()`/`force_stop()` has torn the connection down, or after a failed `start()` left the client in the 'error' state.

Common situations: Forgetting to await start() in async code; calling RPC methods in a script that stopped the client earlier; reusing a client object after context-manager exit (`async with` block ended); a start() failure caught and ignored, then RPC attempted anyway.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/e72ab8a185c8fe18. Report an issue: GitHub.

Appendix: source

Thrown at python/copilot/client.py:1826

            if runtime_path is None:
                raise RuntimeError(
                    f"In-process runtime library not found next to '{explicit_cli}'."
                )
            self._cli_path_source = "environment"
            self._inprocess_cli_entrypoint = explicit_cli
            return runtime_path

        from ._cli_download import ensure_runtime_wrapper

        wrapper_path = Path(ensure_runtime_wrapper())
        self._cli_path_source = "downloaded"
        return str(wrapper_path.with_name("runtime.node"))

    @property
    def rpc(self) -> ServerRpc:
        """Typed server-scoped RPC methods."""
        if self._rpc is None:
            raise RuntimeError("Client is not connected. Call start() first.")
        return self._rpc

    @property
    def runtime_port(self) -> int | None:
        """TCP port the runtime is listening on, when using TCP transport.

        Useful for multi-client scenarios where a second client needs to connect
        to the same runtime. Only available after :meth:`start` completes and
        only when not using stdio transport.
        """
        return self._runtime_port

    def _parse_cli_url(self, url: str) -> tuple[str, int]:
        """
        Parse CLI URL into host and port.

        Supports formats: "host:port", "[ipv6]:port", "http://host:port",
        "https://host:port", or just "port".

View on GitHub (pinned to cd8cf15dc3)