{"record":{"id":"7bf378d5fa624f72","repo":"github/copilot-sdk","slug":"client-not-started-call-start-first","errorCode":null,"errorMessage":"Client not started. Call start() first.","messagePattern":"Client not started\\. Call start\\(\\) first\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"python/copilot/_jsonrpc.py","lineNumber":175,"sourceCode":"                Use this to perform state mutations (for example, registering\n                a server-assigned session id) that must be visible before any\n                subsequent notification on the same connection is dispatched.\n                The callback receives the parsed JSON result. If the callback\n                raises, the exception is propagated to the awaiter.\n\n        Returns:\n            The result from the response\n\n        Raises:\n            JsonRpcError: If the server returns an error\n            asyncio.TimeoutError: If the request times out (only when timeout is set)\n        \"\"\"\n        request_start = time.perf_counter()\n        request_id = str(uuid.uuid4())\n\n        # Use the stored loop to ensure consistency with the reader thread\n        if not self._loop:\n            raise RuntimeError(\"Client not started. Call start() first.\")\n\n        future = self._loop.create_future()\n        with self._pending_lock:\n            self.pending_requests[request_id] = future\n            if on_response_inline is not None:\n                self._pending_inline_callbacks[request_id] = on_response_inline\n\n        message = {\n            \"jsonrpc\": \"2.0\",\n            \"id\": request_id,\n            \"method\": method,\n            \"params\": params or {},\n        }\n\n        try:\n            await self._send_message(message)\n            if timeout is not None:\n                result = await asyncio.wait_for(future, timeout=timeout)","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/python/copilot/_jsonrpc.py#L157-L193","documentation":"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.","triggerScenarios":"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.","commonSituations":"Forgetting to call start() after constructing the client; reusing a client after stop/dispose; a race where a request is issued during startup.","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."],"exampleFix":"# before\nclient = JsonRpcClient(...)\nawait client.request(\"ping\")  # raises\n# after\nclient = JsonRpcClient(...)\nclient.start()\nawait client.request(\"ping\")","handlingStrategy":"validation","validationCode":"if not client.is_running:\n    client.start()","typeGuard":"def client_ready(client) -> bool:\n    return getattr(client, \"_loop\", None) is not None","tryCatchPattern":"try:\n    resp = await client.request(method, params)\nexcept RuntimeError as e:\n    if \"Client not started\" in str(e):\n        client.start(); resp = await client.request(method, params)","preventionTips":["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."],"tags":["jsonrpc","lifecycle","not-started"],"backgroundTag":"invalid-state-transition","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}