puppeteer/puppeteer · error · TargetCloseError
Protocol error (${method}): Session closed. Most likely the
Error message
Protocol error (${method}): Session closed. Most likely the page has been closed. What it means
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.
Source
Thrown at packages/puppeteer-core/src/bidi/CDPSession.ts:78
return undefined;
}
override get detached(): boolean {
return this.#detached;
}
override async send<T extends keyof ProtocolMapping.Commands>(
method: T,
params?: ProtocolMapping.Commands[T]['paramsType'][0],
options?: CommandOptions,
): Promise<ProtocolMapping.Commands[T]['returnType']> {
if (this.#connection === undefined) {
throw new UnsupportedOperation(
'CDP support is required for this feature. The current browser does not support CDP.',
);
}
if (this.#detached) {
throw new TargetCloseError(
`Protocol error (${method}): Session closed. Most likely the page has been closed.`,
);
}
const session = await this.#sessionId.valueOrThrow();
const {result} = await this.#connection.send(
'goog:cdp.sendCommand',
{
method: method,
params: params,
session,
},
options?.timeout,
);
return result.result;
}
override async detach(): Promise<void> {
if (View on GitHub (pinned to d484e21c17)
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.
Example fix
// before
await page.close();
await session.send('Page.navigate', { url }); // throws
// after
await page.close();
if (!session.detached) {
await session.send('Page.navigate', { url });
} Defensive patterns
Strategy: try-catch
Validate before calling
if (session.detached) throw new Error('Session already detached'); Type guard
const isAlive = (s: CDPSession) => !s.detached;
Try / catch
try { return await session.send(method, params); } catch (e) { if (e instanceof TargetCloseError) { /* target gone; clean up */ return; } throw e; } Prevention
- Drop CDPSession references in the same finally block that closes the page.
- Avoid issuing CDP commands that outlive the page lifecycle.
When it happens
Trigger: Calling session.send(...) after page.close() / target.destroy() / browser.disconnect() has fired the session's onClose (which sets #detached = true).
Common situations: 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.
Related errors
- Frame detached.
- Protocol error (${method}): Session with given id not found.
- ${PROCESS_ERROR_EXPLANATION}\nError cause: ${isErrorLike(err
- Must start a new Touch first
- Unknown CDP session with id ${id}
AI-assisted analysis of puppeteer/puppeteer@d484e21c17 (2026-08-12).
Data as JSON: /api/errors/3873d16e20e82632.
Report an issue: GitHub.