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
- Retry the tab creation — transient attach races usually succeed on a second attempt.
- Verify the headless browser is healthy (not crashed) by checking `browser.isConnected()`; restart it if not.
- Ensure `createTarget` is called with `url: "about:blank"` (or similar) so the target is a real page.
- 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
- Create targets with an explicit url so they are real pages
- Close orphan targets on failure so retries start clean
- Check browser.isConnected() before creating tabs
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
- Connected to ${cdpUrl} but puppeteer.connect failed: ${(err
- Target id unavailable from CDP target info
- Target id unavailable from CDP target info
- Target ${payload.targetId} is no longer available on the att
- Target ${targetId} is no longer available on the attached br
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/de03a0b293edaa1c.
Report an issue: GitHub.