can1357/oh-my-pi · error · ToolError

No page target matched ${JSON.stringify(options.matcher)}. A

Error message

No page target matched ${JSON.stringify(options.matcher)}. Available pages:\n${summary}

What it means

pickPageFromList matches an optional matcher string against each page's URL and title (case-insensitive substring). If a matcher is supplied and no page matches, it throws ToolError listing JSON.stringify(matcher) plus every available page as "- title url" so the caller can pick a correct needle. Without a matcher the function instead skips non-usable targets.

Source

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

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();
		const hit = enriched.find(p => p.url.toLowerCase().includes(needle) || p.title.toLowerCase().includes(needle));
		if (hit) return hit.page;
		const summary = enriched.map(p => `- ${p.title || "(untitled)"}  ${p.url}`).join("\n");
		throw new ToolError(`No page target matched ${JSON.stringify(options.matcher)}. Available pages:\n${summary}`);
	}
	const usable = enriched.filter(
		p => !ATTACH_TARGET_SKIP_PATTERN.test(p.url) && !ATTACH_TARGET_SKIP_PATTERN.test(p.title),
	);
	if (options.preferVisible && usable.length > 1) {
		// Best-effort foreground probe; a tab that cannot answer counts as hidden.
		const visibility = await Promise.all(
			usable.map(async p => {
				try {
					return (await p.page.evaluate(() => document.visibilityState === "visible")) === true;
				} catch {
					return false;
				}
			}),
		);
		const foreground = visibility.indexOf(true);
		if (foreground >= 0) return usable[foreground]!.page;
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the "Available pages" list in the error and copy an exact URL/title substring as the matcher
  2. Use a shorter, distinctive substring (e.g. host or route) rather than an exact full URL
  3. Re-enumerate pages — the target list may have changed (tabs opened/closed)
  4. Drop the matcher to let the tool pick the best usable page automatically
  5. Remember matching is substring + lowercase: avoid regex syntax and case-sensitive assumptions

Example fix

// before
await pickPage({ matcher: "MyApp — Dashboard" }); // em-dash/case mismatch
// after
await pickPage({ matcher: "dashboard" }); // loose substring from the listed URLs
Defensive patterns

Strategy: validation

Validate before calling

const pages = await fetch(`${cdpUrl}/json/list`).then(r => r.json());
const match = pages.find(p =>
  (p.title + p.url).toLowerCase().includes(matcher.toLowerCase()));
if (!match) throw new Error(`matcher ${matcher} not found in: ${pages.map(p => p.url).join(", ")}`);

Try / catch

try {
  const tab = await pickPage({ matcher });
} catch (e) {
  if (e instanceof ToolError && e.message.startsWith("No page target matched")) {
    // message lists available pages — pick a substring from it and retry
  } else throw e;
}

Prevention

When it happens

Trigger: Calling page-attach with options.matcher set to a substring that appears in no page URL or title; matcher casing/encoding mismatch (the compare lowercases both sides); matching against a page that closed between enumeration and match; matcher pointing at devtools/extension pages that were skipped from the usable list but present in the summary.

Common situations: Assuming title contains an app name it doesn't; URL uses a different host/port/path than the matcher; SPA route changed after load; typing a regex instead of a substring — matcher is a plain substring, not a pattern.

Related errors


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