can1357/oh-my-pi · error · ToolError

Timed out after ${timeoutMs}ms closing ${tab.kindTag} browse

Error message

Timed out after ${timeoutMs}ms closing ${tab.kindTag} browser tab ${JSON.stringify(tab.name)}; pending resource: ${pendingResource}

What it means

waitForTabCleanup wraps a tab-cleanup promise with a timeout via withTimeout. If the underlying close/dispose does not resolve within timeoutMs, it throws a ToolError reporting which browser tab was being closed, which tab kind, and the resource that was still pending. It exists so a hung browser close cannot stall the tool call indefinitely.

Source

Thrown at packages/coding-agent/src/tools/browser/tab-supervisor.ts:201

	(error as ReportedInitFailure)[REPORTED_INIT_FAILURE] = true;
	return error;
}

function isReportedInitFailure(error: unknown): boolean {
	return error instanceof Error && (error as ReportedInitFailure)[REPORTED_INIT_FAILURE] === true;
}

async function waitForTabCleanup<T>(
	tab: TabSession,
	timeoutMs: number,
	pendingResource: string,
	promise: Promise<T>,
): Promise<T> {
	const message = `Timed out after ${timeoutMs}ms closing ${tab.kindTag} browser tab ${JSON.stringify(tab.name)}; pending resource: ${pendingResource}`;
	try {
		return await withTimeout(promise, timeoutMs, message);
	} catch (error) {
		if (error instanceof Error && error.message === message) throw new ToolError(message);
		throw error;
	}
}

export function getTab(name: string): TabSession | undefined {
	return tabs.get(name);
}

export function acquireTab(name: string, browser: BrowserHandle, opts: AcquireTabOptions): Promise<AcquireTabResult> {
	// Keep the supervisor's Puppeteer handle connected until initialization,
	// worker termination, and abandoned-target cleanup have all been scheduled.
	// The tool caller's outer timeout can release its own lease before this
	// promise settles; without an acquisition-owned hold, cleanup would then
	// run through a disconnected handle and leave the worker's page behind.
	holdBrowser(browser);
	const prior = acquireChains.get(name) ?? Promise.resolve();
	const acquisition = prior.then(() => acquireTabImpl(name, browser, opts));
	const result = acquisition.then(

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry closing the tab; a second close typically targets the already-dead handle and resolves or cleans up state.
  2. Check for dialog-related blockers (dialog policy) and dismiss pending dialogs before closing.
  3. Kill the underlying browser instance (browser close with kill) to force cleanup, then reopen the tab.
  4. Increase the timeout passed to the close/open action if the tab legitimately takes long to close.
  5. Report if a specific site reliably hangs teardown — use action to kill the tab instead of graceful close.

Example fix

// before
await browser({ action: "close", name: "docs" }); // hangs site with beforeunload dialog
// after
await browser({ action: "close", name: "docs", dialogs: "accept" }); // auto-dismiss blocking dialogs
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure no blocking dialogs are expected and the tab exists
const tab = getTab(name);
if (!tab) throw new Error(`Tab ${name} not open; nothing to close`);

Try / catch

try {
  await releaseTab(tab);
} catch (e) {
  if (e instanceof ToolError && /Timed out .* closing .* browser tab/.test(e.message)) {
    await killTab(tab.name); // force cleanup, reopen later
  } else throw e;
}

Prevention

When it happens

Trigger: Calling releaseTab (or any path that awaits waitForTabCleanup) when the browser page/tab teardown hangs — e.g. a stuck beforeunload dialog, a CDP close command that never resolves, or a worker that refuses to terminate within its budget.

Common situations: A page registered dialogs ('beforeunload') that block close; a browser process that is zombie/frozen; slow network teardown of a page with in-flight requests; opening the same tab concurrently from two tool calls so one release waits on the other.

Understand the failure class

Related errors


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