can1357/oh-my-pi · error · ToolError
tab.extract(${JSON.stringify(format)}) produced empty ${form
Error message
tab.extract(${JSON.stringify(format)}) produced empty ${format} content for ${url} What it means
CmuxTab.extract() threw this ToolError because the readability extractor produced a result object but the requested format's field (readable.markdown or readable.text) was empty/undefined. The page had extractable structure, yet the converter emitted zero characters for the requested output format.
Source
Thrown at packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts:547
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) {
await this.scrollIntoView(opts.selector);
captureNotes.push(
`selector ${JSON.stringify(opts.selector)} was scrolled into view, but this surface cannot clip to an element — the image is the full viewport`,
);View on GitHub (pinned to 9690622007)
Solutions
- Try the other format (tab.extract('text') vs tab.extract('markdown')) — one serializer may produce content where the other returns empty.
- Verify the page actually has text by running tab.evaluate(() => document.body.innerText.length) before extracting.
- Fall back to tab.evaluate() with document.body.innerText or a targeted selector's textContent for image-only or oddly structured pages.
- Ensure the page is fully loaded and scrolled (lazy-loaded content may not be in the snapshot HTML).
Example fix
// before: single format, fails on image-heavy page
const md = await tab.extract("markdown");
// after: fall back across formats and to raw text
let content: string;
try {
content = await tab.extract("markdown");
} catch {
content = await tab.extract("text").catch(() => tab.evaluate(() => document.body.innerText));
} Defensive patterns
Strategy: fallback
Validate before calling
const textLen = await tab.evaluate(() => document.body.innerText.length);
if (textLen === 0) throw new Error("page contains no text; extraction will be empty"); Try / catch
let content: string;
try {
content = await tab.extract("markdown");
} catch (err) {
if (err instanceof ToolError && err.message.includes("produced empty")) {
content = await tab.extract("text");
} else throw err;
} Prevention
- Verify the page has text nodes via document.body.innerText before choosing extraction
- Fall back across formats (markdown → text → raw innerText) instead of failing
- Expect empty output on image/canvas-only pages and handle that case explicitly
- Ensure lazy-loaded content is scrolled into the DOM before snapshotting
When it happens
Trigger: Calling tab.extract('text') (or 'markdown') on a page whose readable content converts to an empty string — e.g. content that is only images/embeds with no text nodes, or an extractor bug where markdown conversion drops all nodes while text extraction would succeed (or vice versa).
Common situations: Image-only pages (galleries, canvas-heavy apps) where there is genuinely no text; pages whose entire body is one <img> or <video>; extraction formats where the readability library's markdown serializer silently drops certain node types.
Related errors
- tab.extract(${JSON.stringify(format)}) found no readable con
- 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 !==
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/45bafc29058bfcec.
Report an issue: GitHub.