can1357/oh-my-pi · info · ToolAbortError
Browser open aborted
Error message
Browser open aborted
What it means
acquireBrowser() checks opts.signal?.aborted immediately before launching a browser (and again after the launch resolves). If the caller's AbortSignal already fired, it throws ToolAbortError('Browser open aborted') so no Chromium process is ever spawned for an already-cancelled request. This is deliberate, expected cancellation flow, not a fault in the browser layer.
Source
Thrown at packages/coding-agent/src/tools/browser/registry.ts:119
appArgs?: string[];
signal?: AbortSignal;
}
export async function acquireBrowser(kind: BrowserKind, opts: AcquireBrowserOptions): Promise<BrowserHandle> {
const key = browserKey(kind);
for (;;) {
const existing = browsers.get(key);
if (existing) {
if ("client" in existing) return existing;
if (existing.browser.connected) return existing;
browsers.delete(key);
await disposeBrowserHandle(existing, { kill: false });
continue;
}
// Short-circuit before launching: the tool wrapper's `untilAborted` only
// rejects its outer promise on abort; without this check `openBrowserHandle`
// would still fire and its result would land in `browsers` below.
if (opts.signal?.aborted) throw new ToolAbortError("Browser open aborted");
// Single-flight per key: a concurrent caller already opening this browser
// wins; everyone else waits and re-reads the registry. Without this, N
// simultaneous opens each launch a Chromium and the last write wins,
// leaking the rest as unreferenced process trees.
const pending = pendingOpens.get(key);
if (pending) {
await pending.catch(() => undefined);
continue;
}
const open = openBrowserHandle(kind, opts).finally(() => pendingOpens.delete(key));
pendingOpens.set(key, open);
const handle = await open;
// The launch may resolve AFTER the caller has already aborted (the outer
// `untilAborted` rejects immediately on abort but does not cancel the
// inner promise, and `launchHeadlessBrowser` does not accept a signal).
// Without this branch the completed handle sits in `browsers` at
// refCount:0 forever — no tab ever takes a hold, `releaseBrowser` neverView on GitHub (pinned to 9690622007)
Solutions
- No fix needed — handle it as cancellation: catch ToolAbortError and return a 'cancelled' result instead of treating it as a browser failure
- If aborts are spurious, check what supplies opts.signal (tool wrapper untilAborted / timeout) and raise the timeout or avoid aborting that call
- Clean up is automatic; nothing to dispose manually — the registry disposes any orphaned launch itself
Example fix
// before: treating abort as a crash
try { await acquireBrowser(kind, { signal }); } catch (e) { logger.error(e); }
// after
try { await acquireBrowser(kind, { signal }); }
catch (e) {
if (e instanceof ToolAbortError) return { status: "cancelled" };
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (signal?.aborted) {
// don't call acquireBrowser at all
return cancelledResult;
} Type guard
function isAbort(err: unknown): err is ToolAbortError {
return err instanceof ToolAbortError;
} Try / catch
try {
const handle = await acquireBrowser(kind, { cwd, signal });
} catch (err) {
if (isAbort(err)) return { cancelled: true }; // expected cancellation, not a browser failure
throw err;
} Prevention
- Check signal.aborted before initiating browser work to skip the call entirely
- Treat ToolAbortError('Browser open aborted') as user cancellation in tool wrappers, distinct from launch failures
- If aborts arrive prematurely, review the timeout/untilAborted wiring that owns the signal
- Reuse a single browser per key via the registry instead of launching per call, shrinking the abort window
When it happens
Trigger: The tool call's AbortSignal was cancelled (user pressed escape / request aborted) while the registry was still iterating existing handles or just before openBrowserHandle() was invoked; also thrown post-launch if the signal aborted while the launch was in flight (line 141-148), with the orphan handle disposed first.
Common situations: User cancels a browser_open tool call while the (slow) launch is starting; timeouts cancelling the signal during first-use Chromium download+launch; parallel tool calls aborted together.
Related errors
- Request was aborted
- Auth broker request aborted
- OAuth refresh ownership aborted by caller
- Request was aborted.
- AbortError
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/8de82f056129791a.
Report an issue: GitHub.