can1357/oh-my-pi · error · ToolError

Browser open timed out after ${timeoutMs}ms

Error message

Browser open timed out after ${timeoutMs}ms

What it means

browser open wraps the entire open sequence (browser acquisition, tab queueing, worker startup, page navigation) under a requested timeout. If the timeout signal aborts before open completes — and the abort was not caller cancellation — this ToolError is thrown with the effective timeoutMs.

Source

Thrown at packages/coding-agent/src/tools/browser.ts:357

			const tab = result.tab;
			const url = tab.info.url;
			const title = tab.info.title ?? "";
			details.url = url;
			details.viewport = tab.info.viewport;
			const verb = result.created ? "Opened" : "Reused";
			const lines = [
				`${verb} tab ${JSON.stringify(name)} on ${describeBrowser(browser)}`,
				`URL: ${url}`,
				title ? `Title: ${title}` : null,
			].filter((l): l is string => typeof l === "string");
			details.result = lines.join("\n");
			return toolResult(details).text(lines.join("\n")).done();
		} catch (error) {
			// Caller cancellation stays a ToolAbortError; the requested timeout
			// becomes a timeout ToolError; anything else passes through unchanged.
			if (signal?.aborted) throw error instanceof ToolAbortError ? error : new ToolAbortError();
			if (timeoutSignal.aborted) throw new ToolError(`Browser open timed out after ${timeoutMs}ms`);
			throw error;
		}
	}

	async #close(
		name: string,
		params: BrowserParams,
		details: BrowserToolDetails,
		timeoutMs: number,
		signal?: AbortSignal,
	): Promise<AgentToolResult<BrowserToolDetails>> {
		const kill = !!params.kill;
		if (params.all) {
			const count = await untilAborted(signal, () => releaseAllTabs({ kill, timeoutMs }));
			details.result = `Released ${count} managed tab${count === 1 ? "" : "s"}`;
			return toolResult(details).text(details.result).done();
		}
		const closed = await untilAborted(signal, () => releaseTab(name, { kill, timeoutMs }));

View on GitHub (pinned to 9690622007)

Solutions

  1. Increase the open timeout parameter for cold starts or slow endpoints
  2. Verify the browser binary/CDP endpoint is reachable before opening (the hang is upstream of the timeout)
  3. Pre-warm the browser (open once early) so later opens are fast
  4. Open tabs sequentially or raise the timeout when opening many at once

Example fix

// before
await browser.run({ action: 'open', url, timeoutMs: 5000 })
// after
await browser.run({ action: 'open', url, timeoutMs: 30000 })
Defensive patterns

Strategy: retry

Validate before calling

// Sanity-check the endpoint before spending the open budget
const up = await fetch(cdpHttpUrl('/json/version'), { signal: AbortSignal.timeout(2000) }).then(r => r.ok).catch(() => false);
if (!up) throw new Error('browser endpoint unreachable');

Try / catch

try {
	await browser.run({ action: 'open', url, timeoutMs });
} catch (err) {
	if (err instanceof ToolError && /timed out/.test(err.message)) {
		await Bun.sleep(1000);
		await browser.run({ action: 'open', url, timeoutMs: timeoutMs * 2 }); // retry with backoff + larger budget
	} else throw err;
}

Prevention

When it happens

Trigger: Opening a tab when the browser must cold-start (slow machine, first launch); CDP discovery/connect hanging on an unreachable endpoint; very large page or slow navigation exceeding the timeout; timeoutMs set too low for the environment.

Common situations: Remote CDP endpoint behind a slow VPN; container without a browser binary causing retry loops until timeout; opening many tabs concurrently so the acquisition queue exceeds the budget; user set a small timeout default in config.

Understand the failure class

Related errors


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