can1357/oh-my-pi · info · ToolAbortError

Browser tab open aborted

Error message

Browser tab open aborted

What it means

acquireTabImpl checks the caller-supplied AbortSignal at dequeue time, before spawning a worker or holding a browser. If the signal is already aborted when the serialized open gets its turn, it throws a ToolAbortError instead of doing wasted work. This is intentional cooperative cancellation, not a fault in the library.

Source

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

}

async function acquireTabImpl(
	name: string,
	browser: BrowserHandle,
	opts: AcquireTabOptions,
): Promise<AcquireTabResult> {
	// Worker-init deadline: the inline-fallback retry passes this same start
	// so it can't restart the budget (which would let a cold import that
	// consumed most of it spend the phase floors again for another full
	// budget). Defaults to a fresh clock; callers whose own deadline started
	// earlier (browser acquisition is not part of this budget) pass theirs
	// through `deadlineStartMs` so that earlier time counts against it.
	const startedAt = opts.deadlineStartMs ?? performance.now();
	// Serialized opens can sit behind a slow predecessor in the per-name
	// chain; honor an abort at dequeue instead of spawning a worker and
	// browser hold nobody is waiting for.
	if (opts.signal?.aborted) {
		throw new ToolAbortError("Browser tab open aborted");
	}
	killedTabs.delete(name);
	// Temporary refCount hold so releasing an existing tab on the SAME browser
	// below cannot drop it to refCount 0 and dispose the instance we are about
	// to reuse (e.g. reopening the sole tab with a different dialogs policy).
	let tempHold = false;
	const existing = tabs.get(name);
	if (existing) {
		if (existing.browser === browser && existing.state === "alive") {
			const requestedCmuxSurface = "client" in browser ? (opts.cmuxSurface ?? browser.surface) : undefined;
			if (existing.backend === "cmux" && existing.cmuxAttachedSurface !== requestedCmuxSurface) {
				holdBrowser(browser);
				tempHold = true;
				await releaseTab(name, { kill: false });
			} else if (opts.dialogs !== undefined && opts.dialogs !== existing.dialogPolicy) {
				holdBrowser(browser);
				tempHold = true;
				await releaseTab(name, { kill: false });

View on GitHub (pinned to 9690622007)

Solutions

  1. Treat as expected cancellation: catch ToolAbortError and return a cancelled result instead of retrying.
  2. If opens keep getting aborted while queued, reduce contention — avoid many concurrent opens of the same tab name.
  3. Re-issue the open with a fresh (non-aborted) signal if the operation should still happen.
  4. Check whether your own deadline/timeout is too aggressive relative to the open timeoutMs.

Example fix

// before
const tab = await acquireTab({ name: "docs", signal: alreadyAbortedSignal }); // throws
// after
if (signal.aborted) return cancelled();
const tab = await acquireTab({ name: "docs", signal });
Defensive patterns

Strategy: try-catch

Validate before calling

if (signal?.aborted) {
  return cancelledResult(); // skip the call entirely
}

Try / catch

try {
  await acquireTab(opts);
} catch (e) {
  if (e instanceof ToolAbortError && e.message === "Browser tab open aborted") {
    return; // expected cancellation
  }
  throw e;
}

Prevention

When it happens

Trigger: The tool call's AbortSignal is aborted while the open request is queued behind a slow predecessor tab open with the same name; the caller cancels the action (user abort or deadline) before the worker starts.

Common situations: User presses escape / agent turn cancelled while a browser open is queued; a timeout deadline that elapsed during the wait; an outer tool runner aborting all in-flight operations.

Related errors


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