{"record":{"id":"13893f6c19eeae4d","repo":"can1357/oh-my-pi","slug":"timed-out-after-timeoutms-ms-closing-tab-kindt","errorCode":null,"errorMessage":"Timed out after ${timeoutMs}ms closing ${tab.kindTag} browser tab ${JSON.stringify(tab.name)}; pending resource: ${pendingResource}","messagePattern":"Timed out after (.+?)ms closing (.+?) browser tab (.+?); pending resource: (.+?)","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/tab-supervisor.ts","lineNumber":201,"sourceCode":"\t(error as ReportedInitFailure)[REPORTED_INIT_FAILURE] = true;\n\treturn error;\n}\n\nfunction isReportedInitFailure(error: unknown): boolean {\n\treturn error instanceof Error && (error as ReportedInitFailure)[REPORTED_INIT_FAILURE] === true;\n}\n\nasync function waitForTabCleanup<T>(\n\ttab: TabSession,\n\ttimeoutMs: number,\n\tpendingResource: string,\n\tpromise: Promise<T>,\n): Promise<T> {\n\tconst message = `Timed out after ${timeoutMs}ms closing ${tab.kindTag} browser tab ${JSON.stringify(tab.name)}; pending resource: ${pendingResource}`;\n\ttry {\n\t\treturn await withTimeout(promise, timeoutMs, message);\n\t} catch (error) {\n\t\tif (error instanceof Error && error.message === message) throw new ToolError(message);\n\t\tthrow error;\n\t}\n}\n\nexport function getTab(name: string): TabSession | undefined {\n\treturn tabs.get(name);\n}\n\nexport function acquireTab(name: string, browser: BrowserHandle, opts: AcquireTabOptions): Promise<AcquireTabResult> {\n\t// Keep the supervisor's Puppeteer handle connected until initialization,\n\t// worker termination, and abandoned-target cleanup have all been scheduled.\n\t// The tool caller's outer timeout can release its own lease before this\n\t// promise settles; without an acquisition-owned hold, cleanup would then\n\t// run through a disconnected handle and leave the worker's page behind.\n\tholdBrowser(browser);\n\tconst prior = acquireChains.get(name) ?? Promise.resolve();\n\tconst acquisition = prior.then(() => acquireTabImpl(name, browser, opts));\n\tconst result = acquisition.then(","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/tab-supervisor.ts#L183-L219","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait browser({ action: \"close\", name: \"docs\" }); // hangs site with beforeunload dialog\n// after\nawait browser({ action: \"close\", name: \"docs\", dialogs: \"accept\" }); // auto-dismiss blocking dialogs","handlingStrategy":"try-catch","validationCode":"// ensure no blocking dialogs are expected and the tab exists\nconst tab = getTab(name);\nif (!tab) throw new Error(`Tab ${name} not open; nothing to close`);","typeGuard":null,"tryCatchPattern":"try {\n  await releaseTab(tab);\n} catch (e) {\n  if (e instanceof ToolError && /Timed out .* closing .* browser tab/.test(e.message)) {\n    await killTab(tab.name); // force cleanup, reopen later\n  } else throw e;\n}","preventionTips":["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"],"tags":["timeout","browser","cleanup"],"backgroundTag":"browser-tab-close-timeout","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}