{"record":{"id":"d90d51356c9d755e","repo":"puppeteer/puppeteer","slug":"session-already-detached-most-likely-the-this","errorCode":null,"errorMessage":"Session already detached. Most likely the ${this.#targetType} has been closed.","messagePattern":"Session already detached\\. Most likely the (.+?) has been closed\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/puppeteer-core/src/cdp/CdpSession.ts","lineNumber":157,"sourceCode":"            object.error.message,\n          );\n        }\n      } else {\n        this.#callbacks.resolve(object.id, object.result);\n      }\n    } else {\n      assert(!object.id);\n      this.emit(object.method, object.params);\n    }\n  }\n\n  /**\n   * Detaches the cdpSession from the target. Once detached, the cdpSession object\n   * won't emit any events and can't be used to send messages.\n   */\n  override async detach(): Promise<void> {\n    if (this.detached) {\n      throw new Error(\n        `Session already detached. Most likely the ${this.#targetType} has been closed.`,\n      );\n    }\n    await this.#connection.send('Target.detachFromTarget', {\n      sessionId: this.#sessionId,\n    });\n    this.#detached = true;\n  }\n\n  /**\n   * @internal\n   */\n  onClosed(): void {\n    this.#callbacks.clear();\n    this.#detached = true;\n    this.emit(CDPSessionEvent.Disconnected, undefined);\n  }\n","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/puppeteer/puppeteer/blob/d484e21c17f6023826fefdcdeeb553e04a7aaed8/packages/puppeteer-core/src/cdp/CdpSession.ts#L139-L175","documentation":"Thrown by CdpSession.detach when called on a session whose detached flag is already true. Detach is a one-shot operation: once a session has been detached (either by a prior detach() call or by an onClosed path), calling detach() again is a programmer error rather than a recoverable state.","triggerScenarios":"Calling session.detach() twice; calling detach() after the session was already closed by the target going away; a finally/cleanup block that detaches unconditionally and runs after an earlier detach succeeded.","commonSituations":"Cleanup hooks that detach without checking state; reusing a session object across try/catch blocks where detach may already have run; teardown logic that races with the browser closing the session.","solutions":["Guard detach() with the session.detached (or isClosed()) flag before calling.","Track detach completion in your own boolean and skip re-detach.","Use a single cleanup path (e.g. using/dispose-style helper) to avoid double detach.","Wrap detach() in a try/catch that ignores the 'already detached' error if idempotency is required."],"exampleFix":"// before\nawait session.detach();\n// ...later, in cleanup\nawait session.detach(); // throws 'Session already detached'\n\n// after: guard with the detached flag\nif (!session.detached) {\n  await session.detach();\n}","handlingStrategy":"type-guard","validationCode":"if (!session.detached) {\n  await session.detach();\n}","typeGuard":"function isDetached(session: import('puppeteer-core').CDPSession): boolean {\n  // CDPSession exposes .detached (or use a tracked boolean)\n  return Boolean((session as any).detached);\n}","tryCatchPattern":"try {\n  await session.detach();\n} catch (e) {\n  if (!/Session already detached/.test((e as Error).message)) throw e;\n  // already detached; nothing to do\n}","preventionTips":["Track detach completion in your own boolean.","Centralize detach in a single cleanup path.","Guard detach() with the detached flag before calling."],"tags":["cdp-session","idempotency","lifecycle","double-call"],"backgroundTag":null,"analyzedSha":"d484e21c17f6023826fefdcdeeb553e04a7aaed8","analyzedAt":"2026-08-12T06:33:19.665Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}