github/copilot-sdk · error · RuntimeError
Client not started. Call start() first.
Error message
Client not started. Call start() first.
What it means
JsonRpcClient.request requires the client's background reader loop to exist; it is created by start(). If request() is called before start(), there is no loop to attach futures to and the library raises this error.
Solutions
- Call client.start() before issuing any requests.
- Recreate or restart the client if its loop was previously stopped.
- Add a lifecycle check that the client is running before sending RPCs.
- Check for races where shutdown happens before pending requests.
Example fix
# before
client = JsonRpcClient(...)
await client.request("ping") # raises
# after
client = JsonRpcClient(...)
client.start()
await client.request("ping") Defensive patterns
Strategy: validation
Validate before calling
if not client.is_running:
client.start() Type guard
def client_ready(client) -> bool:
return getattr(client, "_loop", None) is not None Try / catch
try:
resp = await client.request(method, params)
except RuntimeError as e:
if "Client not started" in str(e):
client.start(); resp = await client.request(method, params) Prevention
- Always call start() immediately after constructing JsonRpcClient.
- Encapsulate client creation in a factory that starts it.
- Handle stop/dispose so requests are not issued afterwards.
When it happens
Trigger: Calling request() (or any RPC relying on it) on a JsonRpcClient instance whose start() has not been called, or after the loop was shut down and set to None.
Common situations: Forgetting to call start() after constructing the client; reusing a client after stop/dispose; a race where a request is issued during startup.
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
- An in-process FFI runtime library is already loaded from
- Cannot log after the factory run has settled
- CLI child process was unexpectedly started in parent…
- CLI process not started
- CLI process not started
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/7bf378d5fa624f72.
Report an issue: GitHub.
Appendix: source
Thrown at python/copilot/_jsonrpc.py:175
Use this to perform state mutations (for example, registering
a server-assigned session id) that must be visible before any
subsequent notification on the same connection is dispatched.
The callback receives the parsed JSON result. If the callback
raises, the exception is propagated to the awaiter.
Returns:
The result from the response
Raises:
JsonRpcError: If the server returns an error
asyncio.TimeoutError: If the request times out (only when timeout is set)
"""
request_start = time.perf_counter()
request_id = str(uuid.uuid4())
# Use the stored loop to ensure consistency with the reader thread
if not self._loop:
raise RuntimeError("Client not started. Call start() first.")
future = self._loop.create_future()
with self._pending_lock:
self.pending_requests[request_id] = future
if on_response_inline is not None:
self._pending_inline_callbacks[request_id] = on_response_inline
message = {
"jsonrpc": "2.0",
"id": request_id,
"method": method,
"params": params or {},
}
try:
await self._send_message(message)
if timeout is not None:
result = await asyncio.wait_for(future, timeout=timeout)View on GitHub (pinned to cd8cf15dc3)