{"record":{"id":"70afe686f9f66ee9","repo":"github/copilot-sdk","slug":"failed-to-detach-session-self-session-id-detai","errorCode":null,"errorMessage":"Failed to detach session {self.session_id}: {detail}","messagePattern":"Failed to detach session (.+?): (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"python/copilot/session.py","lineNumber":3099,"sourceCode":"        This method is idempotent—calling it multiple times is safe and will\n        not raise an error if the session is already disconnected.\n\n        Raises:\n            Exception: If the connection fails (on first disconnect call).\n\n        Example:\n            >>> # Clean up when done — session can still be resumed later\n            >>> await session.disconnect()\n        \"\"\"\n        async with self._disconnect_lock:\n            with self._event_handlers_lock:\n                if self._destroyed:\n                    return\n\n            response = await self._client.request(\"session.detach\", {\"sessionId\": self.session_id})\n            if not response.get(\"success\"):\n                detail = response.get(\"error\") or \"unknown error\"\n                raise RuntimeError(f\"Failed to detach session {self.session_id}: {detail}\")\n\n            self._cancel_pending_external_tools()\n            self._run_disconnect_callback()\n            with self._event_handlers_lock:\n                self._destroyed = True\n                self._event_handlers.clear()\n            with self._tool_handlers_lock:\n                self._tool_handlers.clear()\n            with self._permission_handler_lock:\n                self._permission_handler = None\n            with self._command_handlers_lock:\n                self._command_handlers.clear()\n            with self._elicitation_handler_lock:\n                self._elicitation_handler = None\n            with self._exit_plan_mode_handler_lock:\n                self._exit_plan_mode_handler = None\n            with self._auto_mode_switch_handler_lock:\n                self._auto_mode_switch_handler = None","sourceCodeStart":3081,"sourceCodeEnd":3117,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/python/copilot/session.py#L3081-L3117","documentation":"CopilotSession.disconnect() sends a 'session.detach' JSON-RPC request to the runtime. If the runtime responds without success:true, the SDK raises this RuntimeError with the runtime-provided error detail. It means the backend refused or failed to detach the session, so the session's in-memory handlers are NOT cleaned up and the session object remains usable.","triggerScenarios":"Calling await session.disconnect() (directly or via async-with exit, or via resume_session/_initialize_session teardown paths) when the runtime returns {success: false, error: ...} for session.detach — e.g. the session already terminated server-side, the runtime process crashed or was restarted, or the session ID is no longer known to the backend.","commonSituations":"App shutdown racing a runtime crash; double-disconnect across processes where the server already dropped the session; killing/restarting the Copilot CLI process while sessions are open; network/pipe failure between client and runtime causing a non-success response.","solutions":["Inspect the 'detail' in the message to see the runtime's underlying error (e.g. session not found vs connection failure).","If the runtime already discarded the session, the session is effectively dead — discard the object or call client.delete_session/resume_session as appropriate; guard disconnect with try/except if cleanup-on-exit should be best-effort.","Verify the runtime/CLI process is alive and the transport (stdio/TCP) is healthy before disconnecting; reconnect and retry the disconnect if the transport failed.","Check the session_id was not already detached or deleted elsewhere in your code (idempotency is only local via _destroyed, not server-side)."],"exampleFix":"// before\nawait session.disconnect()\n// after\ntry:\n    await session.disconnect()\nexcept RuntimeError as exc:\n    logger.warning('best-effort disconnect failed: %s', exc)","handlingStrategy":"try-catch","validationCode":"if session._destroyed:\n    pass  # already disconnected locally; skip the RPC\nelse:\n    await session.disconnect()","typeGuard":null,"tryCatchPattern":"try:\n    await session.disconnect()\nexcept RuntimeError as exc:\n    if 'Failed to detach session' in str(exc):\n        logger.warning('detach failed, session may already be gone: %s', exc)\n    else:\n        raise","preventionTips":["Keep one owner of session lifecycle; avoid concurrent disconnect/delete of the same session ID.","Treat disconnect during shutdown as best-effort and log rather than crash.","Monitor runtime/CLI process health so you notice crashes before teardown.","Suppress duplicate disconnects by relying on the SDK's idempotent _destroyed check rather than your own flags."],"tags":["python","json-rpc","session-lifecycle","disconnect"],"backgroundTag":"api-error-response","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}