can1357/oh-my-pi · error · ToolError

tab.ariaSnapshot: selector ${JSON.stringify(selector)} match

Error message

tab.ariaSnapshot: selector ${JSON.stringify(selector)} matched no element

What it means

`tab.ariaSnapshot` accepts an optional selector to scope the accessibility snapshot to a subtree; when `page.$(normalizeSelector(selector))` returns null the selector matched no element and this ToolError is thrown before snapshotting. It prevents silently snapshotting the whole page when a scoping root was requested.

Source

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

								`tab.goto(${JSON.stringify(url)}) timed out after ${budgetBound}ms; pending navigation stopped — retry with a longer tool timeout or waitUntil:"domcontentloaded"`,
							);
						}
						throw err;
					}
				}),
			observe: opts => op("tab.observe()", quickOpMs, sig => this.#collectObservation({ ...opts, signal: sig })),
			ariaSnapshot: (selector, opts) =>
				op(
					selector ? `tab.ariaSnapshot(${JSON.stringify(selector)})` : "tab.ariaSnapshot()",
					quickOpMs,
					async sig => {
						let root: ElementHandle | null = null;
						if (selector) {
							root = (await untilAborted(sig, () =>
								page.$(normalizeSelector(selector)),
							)) as ElementHandle | null;
							if (!root)
								throw new ToolError(
									`tab.ariaSnapshot: selector ${JSON.stringify(selector)} matched no element`,
								);
						}
						try {
							return await untilAborted(sig, () => captureAriaSnapshot(page, root, opts));
						} finally {
							await root?.dispose().catch(() => undefined);
						}
					},
				),
			screenshot: opts =>
				op(describeScreenshot(opts), quickOpMs, sig =>
					this.#captureScreenshot(session, output, screenshots, sig, opts),
				),
			extract: (format = "markdown") =>
				op(`tab.extract(${JSON.stringify(format)})`, quickOpMs, async sig => {
					const html = (await untilAborted(sig, () => page.content())) as string;
					const result = await extractReadableFromHtml(html, page.url(), format);

View on GitHub (pinned to 9690622007)

Solutions

  1. Capture a full-page ariaSnapshot (omit the selector) to inspect actual accessible structure and fix the selector
  2. Wait for the element to appear before snapshotting
  3. Scope the selector to the right frame or use a pierce/shadow-aware selector syntax
  4. Verify the selector with `page.$(selector)` directly to confirm matching

Example fix

// before
const snap = await tab.ariaSnapshot({ selector: '.results-panel' }); // not rendered yet
// after
await tab.wait(() => tab.$('.results-panel'), { timeout: 5000 });
const snap = await tab.ariaSnapshot({ selector: '.results-panel' });
Defensive patterns

Strategy: validation

Validate before calling

if (!(await tab.$(selector))) {
  throw new Error(`selector ${selector} not present yet`);
}
const snap = await tab.ariaSnapshot({ selector });

Try / catch

try {
  return await tab.ariaSnapshot({ selector });
} catch (err) {
  if (err instanceof ToolError && err.message.includes('matched no element')) {
    return await tab.ariaSnapshot(); // full-page fallback
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling ariaSnapshot with a selector that resolves to nothing: typo, wrong frame/iframe context, element not yet rendered, or shadow-DOM piercing needed but not expressed in the selector.

Common situations: Selector written against a different DOM state (pre-render SPA, delayed hydration); CSS selector used where the page uses shadow roots; element inside an iframe not targeted; casing/attribute mismatch.

Related errors


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