can1357/oh-my-pi · error · ToolError

Created headless target ${targetId} did not expose a page

Error message

Created headless target ${targetId} did not expose a page

What it means

createTrackedHeadlessPage creates a new headless tab via CDP, waits for the corresponding puppeteer Target (matched by its private target id), then asks for its Page object. If `target.page()` returns null/undefined — the target exists but is not a page or exposes no page — this ToolError is thrown.

Source

Thrown at packages/coding-agent/src/tools/browser/tab-worker.ts:723

}

async function createTrackedHeadlessPage(browser: Browser, reportTarget: (targetId: string) => void): Promise<Page> {
	const session = await browser.target().createCDPSession();
	let targetId: string;
	try {
		({ targetId } = await session.send("Target.createTarget", { url: "about:blank" }));
		reportTarget(targetId);
	} finally {
		await session.detach().catch(() => undefined);
	}
	const existing = browser.targets().find(target => privateTargetId(target) === targetId);
	const target =
		existing ??
		(await browser.waitForTarget(candidate => privateTargetId(candidate) === targetId, {
			timeout: BROWSER_PROTOCOL_TIMEOUT_MS,
		}));
	const page = await target.page();
	if (!page) throw new ToolError(`Created headless target ${targetId} did not expose a page`);
	return page;
}

async function collectObservationEntries(
	core: WorkerCore,
	node: SerializedAXNode,
	entries: ObservationEntry[],
	options: { viewportOnly: boolean; includeAll: boolean },
): Promise<void> {
	if (options.includeAll || isInteractiveNode(node)) {
		const handle = await node.elementHandle();
		if (handle) {
			let inViewport = true;
			if (options.viewportOnly) {
				try {
					inViewport = await handle.isIntersectingViewport();
				} catch {
					inViewport = false;

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry the tab creation — transient attach races usually succeed on a second attempt.
  2. Verify the headless browser is healthy (not crashed) by checking `browser.isConnected()`; restart it if not.
  3. Ensure `createTarget` is called with `url: "about:blank"` (or similar) so the target is a real page.
  4. Catch and clean up: close the orphan targetId so the next attempt doesn't accumulate dead targets.

Example fix

// before
const page = createTrackedHeadlessPage(browser, targetId);
// after
let page;
try {
  page = await createTrackedHeadlessPage(browser, targetId);
} catch {
  await cdpSend("Target.closeTarget", { targetId }).catch(() => {});
  page = await createTrackedHeadlessPage(browser, await cdpCreateTarget());
}
Defensive patterns

Strategy: retry

Validate before calling

if (!browser.isConnected()) throw new Error("headless browser disconnected; restart before creating tabs");

Try / catch

try {
  const page = await createTrackedHeadlessPage(browser, targetId);
} catch (err) {
  if (err instanceof ToolError && err.message.includes("did not expose a page")) {
    await closeOrphanTarget(targetId);
    return createTrackedHeadlessPage(browser, await newTargetId()); // one retry
  }
  throw err;
}

Prevention

When it happens

Trigger: `Target.createTarget` created a target of a non-page type or a page target that detached before `target.page()` resolved; `waitForTarget` matched the right id but the page never attached.

Common situations: Chromium under heavy load during startup; headless browser crash mid-creation; CDP `createTarget` flags producing a tab-less target; racing browser shutdown in tests.

Related errors


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