{"record":{"id":"5cc27673e202d8a6","repo":"can1357/oh-my-pi","slug":"tab-extract-json-stringify-format-found-no-re","errorCode":null,"errorMessage":"tab.extract(${JSON.stringify(format)}) found no readable content on ${url}","messagePattern":"tab\\.extract\\((.+?)\\) found no readable content on (.+?)","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts","lineNumber":543,"sourceCode":"\n\tasync scrollIntoView(selector: string): Promise<void> {\n\t\tawait this.#selectorAction(selector, \"scrollIntoView\");\n\t}\n\n\tasync select(selector: string, ...values: string[]): Promise<string[]> {\n\t\treturn await this.#selectorAction<string[]>(selector, \"select\", { values });\n\t}\n\n\tasync extract(format: ReadableFormat = \"markdown\"): Promise<string> {\n\t\tconst result = (await this.#request(\"browser.snapshot\", { interactive: false })) as CmuxSnapshotResult;\n\t\tconst html = typeof result.page?.html === \"string\" ? result.page.html : \"\";\n\t\tconst url =\n\t\t\t(typeof result.url === \"string\" && result.url.length > 0 ? result.url : undefined) ??\n\t\t\t(typeof result.page?.url === \"string\" && result.page.url.length > 0 ? result.page.url : undefined) ??\n\t\t\tthis.#lastUrl;\n\t\tconst readable = await extractReadableFromHtml(html, url, format);\n\t\tif (!readable) {\n\t\t\tthrow new ToolError(`tab.extract(${JSON.stringify(format)}) found no readable content on ${url}`);\n\t\t}\n\t\tconst content = format === \"markdown\" ? readable.markdown : readable.text;\n\t\tif (!content) {\n\t\t\tthrow new ToolError(`tab.extract(${JSON.stringify(format)}) produced empty ${format} content for ${url}`);\n\t\t}\n\t\treturn content;\n\t}\n\n\tasync screenshot(opts: ScreenshotOptions = {}): Promise<string> {\n\t\tconst context = this.#requireRunContext(\"tab.screenshot()\");\n\t\t// The cmux daemon's `browser.screenshot` captures the surface viewport\n\t\t// only — it has no element-clip or full-page mode, and Bun.Image cannot\n\t\t// crop locally. Degrade transparently instead of silently mislabeling\n\t\t// the capture: scroll the element into view, then TELL the model the\n\t\t// image is the full viewport (reports showed selector captures being\n\t\t// consumed as element crops).\n\t\tconst captureNotes: string[] = [];\n\t\tif (opts.selector) {","sourceCodeStart":525,"sourceCodeEnd":561,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts#L525-L561","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before: extract immediately after goto on a slow SPA\nawait tab.goto(url);\nconst md = await tab.extract();\n// after: wait for rendered content first\nawait tab.goto(url);\nawait tab.waitForSelector(\"main article\");\nconst md = await tab.extract();","handlingStrategy":"fallback","validationCode":"// ensure there is HTML to extract and the page has rendered\nawait tab.waitForSelector(\"body\");\nconst hasHtml = await tab.evaluate(() => document.body.innerText.trim().length > 0);\nif (!hasHtml) throw new Error(\"page has no text content to extract\");","typeGuard":null,"tryCatchPattern":"let content: string;\ntry {\n  content = await tab.extract(\"markdown\");\n} catch (err) {\n  if (err instanceof ToolError && err.message.includes(\"found no readable content\")) {\n    content = await tab.evaluate(() => document.body.innerText);\n  } else throw err;\n}","preventionTips":["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"],"tags":["browser","content-extraction","readability","spa"],"backgroundTag":"no-readable-content-extracted","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}