can1357/oh-my-pi · error · ToolError

No page targets available on the attached browser

Error message

No page targets available on the attached browser

What it means

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.

Source

Thrown at packages/coding-agent/src/tools/browser/attach.ts:206

 */
export async function pickElectronTarget(
	browser: Browser,
	options: { matcher?: string; preferVisible?: boolean } = {},
): Promise<Page> {
	const discoveredPages = await Promise.all(
		browser.targets().map(async target => {
			if (String(target.type()) !== "page") return null;
			return await target.page().catch(() => null);
		}),
	);
	const usablePages = discoveredPages.filter((page): page is Page => page !== null);
	if (usablePages.length > 0) {
		return pickPageFromList(usablePages, options);
	}

	const fallbackPages = await browser.pages();
	if (!fallbackPages.length) {
		throw new ToolError("No page targets available on the attached browser");
	}
	return pickPageFromList(fallbackPages, options);
}

async function enrichPages(pages: Page[]): Promise<Array<{ page: Page; url: string; title: string }>> {
	return await Promise.all(
		pages.map(async page => ({
			page,
			url: page.url(),
			title: ((await page.title().catch(() => "")) ?? "").trim(),
		})),
	);
}

async function pickPageFromList(pages: Page[], options: { matcher?: string; preferVisible?: boolean }): Promise<Page> {
	const enriched = await enrichPages(pages);
	if (options.matcher) {
		const needle = options.matcher.toLowerCase();

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure the Electron app has at least one open window before attaching
  2. Retry attach after the app finishes launching (poll for targets)
  3. Start the app with a window (do not use windowless/headless launch)
  4. Verify the CDP endpoint belongs to the actual app process, not a helper
  5. Relaunch the app with remote debugging enabled and a visible window

Example fix

// before
const tab = await attachElectron({ cdpUrl }); // app has no windows yet
// after
await waitForCondition(() => electronHasWindow()); // app created its window
const tab = await attachElectron({ cdpUrl });
Defensive patterns

Strategy: retry

Validate before calling

const targets = await fetch(`${cdpUrl}/json/list`).then(r => r.json());
const pages = targets.filter(t => t.type === "page");
if (!pages.length) throw new Error("wait for the Electron app to open a window before attaching");

Try / catch

try {
  const tab = await attachElectron(opts);
} catch (e) {
  if (e instanceof ToolError && e.message === "No page targets available on the attached browser") {
    await Bun.sleep(1000); // app still launching; retry
    return attachWithRetry(opts, attemptsLeft - 1);
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/897c73cdf416939a. Report an issue: GitHub.