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
- Wait for the page to finish loading first (tab.waitForNavigation() or tab.waitForSelector() on a known content element) before calling tab.extract().
- Call tab.waitFor('<content selector>') to ensure the app has rendered, then retry extract().
- Fall back to tab.evaluate() with a custom extraction function (e.g. document.body.innerText) when the readability algorithm fails on a valid page.
- 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
- Always wait for a known content selector before extracting on SPAs
- Prefer tab.evaluate-based extraction for non-article layouts (dashboards, apps)
- Check the final URL after navigation to rule out interstitial/error pages
- Remember readability ignores iframe content — hoist or bypass iframes
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
- tab.extract(${JSON.stringify(format)}) produced empty ${form
- Browser selector must be a string; got ${kind}. tab.click/ty
- tab.ariaSnapshot: selector ${__sel} matched no element
- Timed out waiting for CDP endpoint ${cdpUrl}${lastStatus !==
- No page targets available on the attached browser
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/5cc27673e202d8a6.
Report an issue: GitHub.