can1357/oh-my-pi · error · ToolError

tab.extract(${JSON.stringify(format)}) found no readable con

Error message

tab.extract(${JSON.stringify(format)}) found no readable content on ${url}

What it means

CmuxTab.extract() snapshots the current page HTML and runs it through a readability extractor (extractReadableFromHtml). This ToolError is thrown when the extractor returns null — i.e. the readability algorithm could not identify any article/main content in the page's HTML. The cmux browser snapshot returned HTML that either was empty or looked like boilerplate (scripts, iframes, empty shell) with no extractable body.

Source

Thrown at packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts:543

	async scrollIntoView(selector: string): Promise<void> {
		await this.#selectorAction(selector, "scrollIntoView");
	}

	async select(selector: string, ...values: string[]): Promise<string[]> {
		return await this.#selectorAction<string[]>(selector, "select", { values });
	}

	async extract(format: ReadableFormat = "markdown"): Promise<string> {
		const result = (await this.#request("browser.snapshot", { interactive: false })) as CmuxSnapshotResult;
		const html = typeof result.page?.html === "string" ? result.page.html : "";
		const url =
			(typeof result.url === "string" && result.url.length > 0 ? result.url : undefined) ??
			(typeof result.page?.url === "string" && result.page.url.length > 0 ? result.page.url : undefined) ??
			this.#lastUrl;
		const readable = await extractReadableFromHtml(html, url, format);
		if (!readable) {
			throw new ToolError(`tab.extract(${JSON.stringify(format)}) found no readable content on ${url}`);
		}
		const content = format === "markdown" ? readable.markdown : readable.text;
		if (!content) {
			throw new ToolError(`tab.extract(${JSON.stringify(format)}) produced empty ${format} content for ${url}`);
		}
		return content;
	}

	async screenshot(opts: ScreenshotOptions = {}): Promise<string> {
		const context = this.#requireRunContext("tab.screenshot()");
		// The cmux daemon's `browser.screenshot` captures the surface viewport
		// only — it has no element-clip or full-page mode, and Bun.Image cannot
		// crop locally. Degrade transparently instead of silently mislabeling
		// the capture: scroll the element into view, then TELL the model the
		// image is the full viewport (reports showed selector captures being
		// consumed as element crops).
		const captureNotes: string[] = [];
		if (opts.selector) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Wait for the page to finish loading first (tab.waitForNavigation() or tab.waitForSelector() on a known content element) before calling tab.extract().
  2. Call tab.waitFor('<content selector>') to ensure the app has rendered, then retry extract().
  3. Fall back to tab.evaluate() with a custom extraction function (e.g. document.body.innerText) when the readability algorithm fails on a valid page.
  4. Check the URL the tab actually ended up on (tab.waitForUrl or a snapshot) — you may be extracting from a login/error interstitial instead of the target page.

Example fix

// before: extract immediately after goto on a slow SPA
await tab.goto(url);
const md = await tab.extract();
// after: wait for rendered content first
await tab.goto(url);
await tab.waitForSelector("main article");
const md = await tab.extract();
Defensive patterns

Strategy: fallback

Validate before calling

// ensure there is HTML to extract and the page has rendered
await tab.waitForSelector("body");
const hasHtml = await tab.evaluate(() => document.body.innerText.trim().length > 0);
if (!hasHtml) throw new Error("page has no text content to extract");

Try / catch

let content: string;
try {
  content = await tab.extract("markdown");
} catch (err) {
  if (err instanceof ToolError && err.message.includes("found no readable content")) {
    content = await tab.evaluate(() => document.body.innerText);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling tab.extract() (default 'markdown' or 'text' format) when the snapshot's page.html is empty, when the page is a JS-rendered SPA whose DOM the daemon snapshotted before content rendered, when the page is mostly iframes/embeds (readability skips them), or when the document is boilerplate-only (login walls, consent interstitials, error pages).

Common situations: Extracting from single-page apps that hydrate slowly; pages behind Cloudflare/bot-check interstitials; pages whose content lives in an <iframe>; navigating to a URL that failed and landed on a blank or error page; PDFs or non-HTML documents where the snapshot yields no HTML body.

Related errors


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