{"record":{"id":"3873d16e20e82632","repo":"puppeteer/puppeteer","slug":"protocol-error-method-session-closed-most-l","errorCode":null,"errorMessage":"Protocol error (${method}): Session closed. Most likely the page has been closed.","messagePattern":"Protocol error \\((.+?)\\): Session closed\\. Most likely the page has been closed\\.","errorType":"exception","errorClass":"TargetCloseError","httpStatus":null,"severity":"error","filePath":"packages/puppeteer-core/src/bidi/CDPSession.ts","lineNumber":78,"sourceCode":"    return undefined;\n  }\n\n  override get detached(): boolean {\n    return this.#detached;\n  }\n\n  override async send<T extends keyof ProtocolMapping.Commands>(\n    method: T,\n    params?: ProtocolMapping.Commands[T]['paramsType'][0],\n    options?: CommandOptions,\n  ): Promise<ProtocolMapping.Commands[T]['returnType']> {\n    if (this.#connection === undefined) {\n      throw new UnsupportedOperation(\n        'CDP support is required for this feature. The current browser does not support CDP.',\n      );\n    }\n    if (this.#detached) {\n      throw new TargetCloseError(\n        `Protocol error (${method}): Session closed. Most likely the page has been closed.`,\n      );\n    }\n    const session = await this.#sessionId.valueOrThrow();\n    const {result} = await this.#connection.send(\n      'goog:cdp.sendCommand',\n      {\n        method: method,\n        params: params,\n        session,\n      },\n      options?.timeout,\n    );\n    return result.result;\n  }\n\n  override async detach(): Promise<void> {\n    if (","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/puppeteer/puppeteer/blob/d484e21c17f6023826fefdcdeeb553e04a7aaed8/packages/puppeteer-core/src/bidi/CDPSession.ts#L60-L96","documentation":"BidiCdpSession.send() throws TargetCloseError (a ProtocolError subclass) when the session's #detached flag is true, i.e. the underlying target/page was closed but the caller still holds the CDPSession reference and tried to send a command. The message embeds the method name being sent.","triggerScenarios":"Calling session.send(...) after page.close() / target.destroy() / browser.disconnect() has fired the session's onClose (which sets #detached = true).","commonSituations":"Forgetting to drop a CDPSession reference after closing the page; an async operation that outlives the page (e.g. a long Performance.getMetrics call issued just before page.close()); a navigation that tears down the target.","solutions":["Check session.detached before sending, or await page-close before issuing further commands.","Discard CDPSession references in the same finally block that closes the page.","Catch TargetCloseError around long-running CDP calls and treat it as a graceful shutdown."],"exampleFix":"// before\nawait page.close();\nawait session.send('Page.navigate', { url }); // throws\n// after\nawait page.close();\nif (!session.detached) {\n  await session.send('Page.navigate', { url });\n}","handlingStrategy":"try-catch","validationCode":"if (session.detached) throw new Error('Session already detached');","typeGuard":"const isAlive = (s: CDPSession) => !s.detached;","tryCatchPattern":"try { return await session.send(method, params); } catch (e) { if (e instanceof TargetCloseError) { /* target gone; clean up */ return; } throw e; }","preventionTips":["Drop CDPSession references in the same finally block that closes the page.","Avoid issuing CDP commands that outlive the page lifecycle."],"tags":["cdp","lifecycle","target-closed"],"backgroundTag":null,"analyzedSha":"d484e21c17f6023826fefdcdeeb553e04a7aaed8","analyzedAt":"2026-08-12T06:33:19.665Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}