microsoft/playwright · error · Error
No tab found for sessionId: ${sessionId}
Error message
No tab found for sessionId: ${sessionId} What it means
Thrown by the extension browser model's `sendCommand` when a `sessionId` matches neither a relay-level tab session (`pw-tab-N`) nor any tab session's set of child CDP sessions (workers, oopifs). The relay cannot route a CDP command whose session it does not recognize, so it aborts.
Source
Thrown at packages/playwright-core/src/tools/mcp/browserModel.ts:192
method,
params,
]);
}
// Forward a CDP command from Playwright to the tab its sessionId resolves to.
async sendCommand(sessionId: string, method: string, params: any): Promise<any> {
// Two cases:
// 1. sessionId is a relay-level tab session (pw-tab-N) → strip and route by tabId.
// 2. sessionId is a child CDP session (worker, oopif) → route to its owning tab,
// keep the sessionId so the extension forwards it to chrome.debugger.
let tabSession = this._findTabSession(s => s.sessionId === sessionId);
let cdpSessionId: string | undefined;
if (!tabSession) {
tabSession = this._findTabSession(s => s.childSessions.has(sessionId));
cdpSessionId = sessionId;
}
if (!tabSession)
throw new Error(`No tab found for sessionId: ${sessionId}`);
return await this._sendToExtension('chrome.debugger.sendCommand', [
{ tabId: tabSession.tabId, sessionId: cdpSessionId },
method,
params,
]);
}
// ─── Internals ────────────────────────────────────────────────────────
private async _attachTab(tabId: number): Promise<TabSession> {
const existing = this._tabSessions.get(tabId);
if (existing)
return existing;
await this._sendToExtension('chrome.debugger.attach', [{ tabId }, '1.3']);
const result = await this._sendToExtension('chrome.debugger.sendCommand', [
{ tabId },
'Target.getTargetInfo',
]);View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Recover by re-attaching the target (navigate/reattach the tab) so a fresh sessionId is issued.
- Confirm the extension connection is healthy and the target tab is still attached.
- Treat stale-session commands as non-fatal: drop the command and re-issue after re-attach rather than retrying the same id.
Defensive patterns
Strategy: try-catch
Try / catch
try {
await model.sendCommand(sessionId, method, params);
} catch (e) {
if (/No tab found for sessionId/.test((e as Error).message)) {
// session is gone — re-attach the target to obtain a fresh sessionId, then drop the command
return; // or re-issue after re-attach
}
throw e;
} Prevention
- Drop stale sessionIds when targets detach; re-attach to get fresh ones.
- Treat 'No tab found' as a recoverable signal, not a fatal error.
- Keep extension and Playwright versions aligned to avoid session-id drift.
When it happens
Trigger: Playwright sends a CDP command on a sessionId that was never created (typo/foreign id), that belonged to a tab already detached/closed, or to a child session (worker/oopif) whose owning tab has been detached. The two-step lookup (`_findTabSession` by sessionId, then by `childSessions.has`) both fail.
Common situations: A child session (service worker, out-of-process iframe) was destroyed but Playwright still holds its sessionId and sends a command; the extension detached a tab mid-operation; protocol version drift between Playwright and the extension producing session ids the relay didn't record.
Related errors
- No attached tab to forward browser-level command: ${method}
- This frame does not have a separate CDP session, it is a par
- Failed to create tab
- Playwright Extension not found in "${userDataDir}". Install
- Route from har is not supported in thin clients
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/94852e5af0d1f940.
Report an issue: GitHub.