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

  1. Try the other format (tab.extract('text') vs tab.extract('markdown')) — one serializer may produce content where the other returns empty.
  2. Verify the page actually has text by running tab.evaluate(() => document.body.innerText.length) before extracting.
  3. Fall back to tab.evaluate() with document.body.innerText or a targeted selector's textContent for image-only or oddly structured pages.
  4. 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

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


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