{"record":{"id":"5978806ce4eceadc","repo":"microsoft/playwright","slug":"no-attached-tab-to-forward-browser-level-command","errorCode":null,"errorMessage":"No attached tab to forward browser-level command: ${method}","messagePattern":"No attached tab to forward browser-level command: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/tools/mcp/browserModel.ts","lineNumber":171,"sourceCode":"      return { success: false };\n    await this._sendToExtension('chrome.tabs.remove', [tabSession.tabId]);\n    return { success: true };\n  }\n\n  getTargetInfo(sessionId: string | undefined): any {\n    if (!sessionId)\n      return undefined;\n    return this._findTabSession(s => s.sessionId === sessionId)?.targetInfo;\n  }\n\n  // Forward a browser-level CDP command (Storage.*, Browser.*, etc.) that is\n  // not associated with a specific tab. chrome.debugger.sendCommand requires a\n  // target, so we route through any attached tab; the command itself is\n  // browser-scoped and returns the same result regardless of which tab is used.\n  async sendBrowserCommand(method: string, params: any): Promise<any> {\n    const tabSession = this._tabSessions.values().next().value;\n    if (!tabSession)\n      throw new Error(`No attached tab to forward browser-level command: ${method}`);\n    return await this._sendToExtension('chrome.debugger.sendCommand', [\n      { tabId: tabSession.tabId },\n      method,\n      params,\n    ]);\n  }\n\n  // Forward a CDP command from Playwright to the tab its sessionId resolves to.\n  async sendCommand(sessionId: string, method: string, params: any): Promise<any> {\n    // Two cases:\n    // 1. sessionId is a relay-level tab session (pw-tab-N) → strip and route by tabId.\n    // 2. sessionId is a child CDP session (worker, oopif) → route to its owning tab,\n    //    keep the sessionId so the extension forwards it to chrome.debugger.\n    let tabSession = this._findTabSession(s => s.sessionId === sessionId);\n    let cdpSessionId: string | undefined;\n    if (!tabSession) {\n      tabSession = this._findTabSession(s => s.childSessions.has(sessionId));\n      cdpSessionId = sessionId;","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/tools/mcp/browserModel.ts#L153-L189","documentation":"Thrown by the extension browser model's `sendBrowserCommand` when there are no attached tab sessions. Browser-scoped CDP commands (Storage.*, Browser.*, etc.) are forwarded through any attached tab via `chrome.debugger.sendCommand`, which requires a target; with zero tabs attached there is no route, so the command is rejected.","triggerScenarios":"Issuing a browser-level CDP command (e.g. through Playwright's browser-level session) before any tab has been attached via `enableAutoAttach`/`_attachTab`, or after all tabs have been detached/closed.","commonSituations":"Calling a browser-scoped API at the very start of a session before auto-attach completed; all tabs were closed leaving the browser with zero targets; the extension detached all debuggee targets (user closed all tabs or revoked debugger permission).","solutions":["Ensure at least one tab is open and attached before issuing browser-level commands (trigger navigation/auto-attach first).","If all tabs were closed, open a new tab/page (which auto-attaches) and retry.","Confirm the extension still holds debugger attach permissions; re-grant if revoked."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Ensure a tab is attached before issuing browser-level commands.\nfunction hasAttachedTab(model: any): boolean {\n  // _tabSessions is private; expose a count via a public getter if extending.\n  // Otherwise track attachments client-side via attach events.\n  return /* model.tabCount() > 0 */ true;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await model.sendBrowserCommand(method, params);\n} catch (e) {\n  if (/No attached tab/.test((e as Error).message)) {\n    await model.createTarget('about:blank'); // open+attach a tab\n    await model.sendBrowserCommand(method, params);\n  } else throw e;\n}","preventionTips":["Open/attach at least one tab before browser-level CDP calls.","Listen for detach events and stop issuing browser-level commands when all tabs close.","Confirm the extension retains debugger attach permission."],"tags":["extension","cdp","browser-model","tabs","attach"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}