microsoft/playwright · warning · Error

Session already detached. Most likely the page has been clos

Error message

Session already detached. Most likely the page has been closed.

What it means

Thrown by CRConnection.detach() (crConnection.ts:178) when this._closed is already true. _closed is flipped to true by dispose(), which runs when the owning page/frame target is destroyed or when detach ran once before. The message tells the user the page has most likely been closed under them.

Source

Thrown at packages/playwright-core/src/server/chromium/crConnection.ts:178

        callback.reject(callback.error);
      } else {
        callback.resolve(object.result);
      }
    } else if (object.id && object.error?.code === -32001) {
      // Message to a closed session, just ignore it.
    } else {
      assert(!object.id, object?.error?.message || undefined);
      Promise.resolve().then(() => {
        if (this._eventListener)
          this._eventListener(object.method!, object.params);
        (this.emit as any)(object.method as any, object.params);
      });
    }
  }

  async detach() {
    if (this._closed)
      throw new Error(`Session already detached. Most likely the page has been closed.`);
    if (!this._parentSession)
      throw new Error('Root session cannot be closed');
    // Ideally, detaching should resume any target, but there is a bug in the backend,
    // so we must Runtime.runIfWaitingForDebugger first.
    await this._sendMayFail('Runtime.runIfWaitingForDebugger');
    await this._parentSession.send('Target.detachFromTarget', { sessionId: this._sessionId });
    this.dispose();
  }

  dispose() {
    this._closed = true;
    this._connection._sessions.delete(this._sessionId);
    this._rejectPendingCallbacks(`Internal server error, session closed.`);
  }

  private _rejectPendingCallbacks(message: string) {
    for (const callback of this._callbacks.values()) {
      callback.error.setMessage(message);

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Wrap detach() in try/catch and ignore the 'Session already detached' error during teardown.
  2. Track detachment in your own flag and skip the call when already closed.
  3. Detach in a page.close listener rather than relying on out-of-band cleanup.

Example fix

// before
await session.detach();
// after
try { await session.detach(); } catch (e) {
  if (!/Session already detached/.test(String(e))) throw e;
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await session.detach();
} catch (e) {
  const msg = e instanceof Error ? e.message : String(e);
  if (!msg.includes('Session already detached')) throw e;
  // expected during teardown
}

Prevention

When it happens

Trigger: Calling cdpSession.detach() twice; calling detach() in a finally block after page.close() already disposed the session; the page crashed or was closed by the app while cleanup was running.

Common situations: try/finally teardown that detaches unconditionally; reused sessions across navigations that recreate targets; tests that close the page in afterEach while the test body also detached.

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/63e727c25fffde9f. Report an issue: GitHub.