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
- Retry closing the tab; a second close typically targets the already-dead handle and resolves or cleans up state.
- Check for dialog-related blockers (dialog policy) and dismiss pending dialogs before closing.
- Kill the underlying browser instance (browser close with kill) to force cleanup, then reopen the tab.
- Increase the timeout passed to the close/open action if the tab legitimately takes long to close.
- 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
- Enable dialog auto-handling (dialogs policy) for sites with beforeunload prompts
- Avoid graceful-closing tabs with heavy in-flight network activity; kill instead
- Serialize close/open operations on the same tab name
- Raise timeoutMs for tabs known to be slow to tear down
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Timed out waiting for CDP endpoint ${cdpUrl}${lastStatus !==
- tab.waitForUrl() timed out after ${timeoutMs}ms
- tab.waitForNavigation() timed out after ${timeoutMs}ms
- tab.waitForResponse() timed out after ${timeoutMs}ms
- page.waitForFunction() timed out after ${timeoutMs}ms
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/13893f6c19eeae4d.
Report an issue: GitHub.