{"record":{"id":"46f58e78ee4ec1a6","repo":"microsoft/playwright","slug":"root-session-cannot-be-closed","errorCode":null,"errorMessage":"Root session cannot be closed","messagePattern":"Root session cannot be closed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/chromium/crConnection.ts","lineNumber":180,"sourceCode":"        callback.resolve(object.result);\n      }\n    } else if (object.id && object.error?.code === -32001) {\n      // Message to a closed session, just ignore it.\n    } else {\n      assert(!object.id, object?.error?.message || undefined);\n      Promise.resolve().then(() => {\n        if (this._eventListener)\n          this._eventListener(object.method!, object.params);\n        (this.emit as any)(object.method as any, object.params);\n      });\n    }\n  }\n\n  async detach() {\n    if (this._closed)\n      throw new Error(`Session already detached. Most likely the page has been closed.`);\n    if (!this._parentSession)\n      throw new Error('Root session cannot be closed');\n    // Ideally, detaching should resume any target, but there is a bug in the backend,\n    // so we must Runtime.runIfWaitingForDebugger first.\n    await this._sendMayFail('Runtime.runIfWaitingForDebugger');\n    await this._parentSession.send('Target.detachFromTarget', { sessionId: this._sessionId });\n    this.dispose();\n  }\n\n  dispose() {\n    this._closed = true;\n    this._connection._sessions.delete(this._sessionId);\n    this._rejectPendingCallbacks(`Internal server error, session closed.`);\n  }\n\n  private _rejectPendingCallbacks(message: string) {\n    for (const callback of this._callbacks.values()) {\n      callback.error.setMessage(message);\n      callback.error.type = this._crashed ? 'crashed' : 'closed';\n      callback.error.logs = this._connection._browserDisconnectedLogs;","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/server/chromium/crConnection.ts#L162-L198","documentation":"Thrown by CRConnection.detach() (crConnection.ts:180) when this._parentSession is falsy. The root browser-level session has no parent to detach from, so Target.detachFromTarget cannot apply. Root sessions are obtained from _clientRootSession() and are not meant to be detached by users.","triggerScenarios":"Calling detach() on the browser's root CDP client; generic session-walking tooling that detaches every session it finds, including the root; reaching the root after a parent chain was nulled.","commonSituations":"Internal tooling that lists _connection._sessions and detaches each; user code that grabbed the browser-level client for an unrelated command and then tries to detach.","solutions":["Do not detach the root session; only detach sessions created via newCDPSession(page|frame).","Before detaching generically, check that the session has a parent (skip the root)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Only detach sessions you created via newCDPSession.\n// If walking sessions generically, skip the root:\n// root sessions have no parent — do not call detach() on them.\nconst isChildSession = (s: CDPSession) => /* track via your own registry */ true;\nif (isChildSession(session)) await session.detach();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not detach browser-level/root CDP clients.","Maintain an explicit list of sessions you opened and only detach those.","Treat 'Root session cannot be closed' as a programming error, not a runtime condition."],"tags":["cdp","session-lifecycle","chromium","internal"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}