{"record":{"id":"897c73cdf416939a","repo":"can1357/oh-my-pi","slug":"no-page-targets-available-on-the-attached-browser","errorCode":null,"errorMessage":"No page targets available on the attached browser","messagePattern":"No page targets available on the attached browser","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/attach.ts","lineNumber":206,"sourceCode":" */\nexport async function pickElectronTarget(\n\tbrowser: Browser,\n\toptions: { matcher?: string; preferVisible?: boolean } = {},\n): Promise<Page> {\n\tconst discoveredPages = await Promise.all(\n\t\tbrowser.targets().map(async target => {\n\t\t\tif (String(target.type()) !== \"page\") return null;\n\t\t\treturn await target.page().catch(() => null);\n\t\t}),\n\t);\n\tconst usablePages = discoveredPages.filter((page): page is Page => page !== null);\n\tif (usablePages.length > 0) {\n\t\treturn pickPageFromList(usablePages, options);\n\t}\n\n\tconst fallbackPages = await browser.pages();\n\tif (!fallbackPages.length) {\n\t\tthrow new ToolError(\"No page targets available on the attached browser\");\n\t}\n\treturn pickPageFromList(fallbackPages, options);\n}\n\nasync function enrichPages(pages: Page[]): Promise<Array<{ page: Page; url: string; title: string }>> {\n\treturn await Promise.all(\n\t\tpages.map(async page => ({\n\t\t\tpage,\n\t\t\turl: page.url(),\n\t\t\ttitle: ((await page.title().catch(() => \"\")) ?? \"\").trim(),\n\t\t})),\n\t);\n}\n\nasync function pickPageFromList(pages: Page[], options: { matcher?: string; preferVisible?: boolean }): Promise<Page> {\n\tconst enriched = await enrichPages(pages);\n\tif (options.matcher) {\n\t\tconst needle = options.matcher.toLowerCase();","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/attach.ts#L188-L224","documentation":"When attaching to an Electron browser, pickElectronTarget enumerates page targets and falls back to browser.pages(). If both lists are empty there is no page (webContents of type page) to attach to, so it throws ToolError(\"No page targets available on the attached browser\"). The tool cannot proceed without at least one page target.","triggerScenarios":"Attaching to an Electron app that has no open BrowserWindows with page webContents; app still booting before its first window is created; all windows closed before attach; targets filtered out by the usable-page skip pattern leaving zero fallback pages.","commonSituations":"Attaching during Electron app startup before window creation; headless/service Electron processes without UI windows; windows closed by the app after launch; attaching to the wrong process (main/utility worker instead of the renderer host).","solutions":["Ensure the Electron app has at least one open window before attaching","Retry attach after the app finishes launching (poll for targets)","Start the app with a window (do not use windowless/headless launch)","Verify the CDP endpoint belongs to the actual app process, not a helper","Relaunch the app with remote debugging enabled and a visible window"],"exampleFix":"// before\nconst tab = await attachElectron({ cdpUrl }); // app has no windows yet\n// after\nawait waitForCondition(() => electronHasWindow()); // app created its window\nconst tab = await attachElectron({ cdpUrl });","handlingStrategy":"retry","validationCode":"const targets = await fetch(`${cdpUrl}/json/list`).then(r => r.json());\nconst pages = targets.filter(t => t.type === \"page\");\nif (!pages.length) throw new Error(\"wait for the Electron app to open a window before attaching\");","typeGuard":null,"tryCatchPattern":"try {\n  const tab = await attachElectron(opts);\n} catch (e) {\n  if (e instanceof ToolError && e.message === \"No page targets available on the attached browser\") {\n    await Bun.sleep(1000); // app still launching; retry\n    return attachWithRetry(opts, attemptsLeft - 1);\n  }\n  throw e;\n}","preventionTips":["Attach only after the app has created its first window","Launch Electron with a visible window (not headless/windowless)","Confirm the CDP port belongs to the app process, not a helper child","Poll /json/list for type==page targets before attaching"],"tags":["browser","electron","cdp","attach"],"backgroundTag":"no-browser-page-target","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}