can1357/oh-my-pi · error · ToolError

Shared browser attach failed: ${err instanceof Error ? err.m

Error message

Shared browser attach failed: ${err instanceof Error ? err.message : String(err)}

What it means

The final catch-all of openSharedHeadlessHandle: any exception during shared-daemon setup that is not already a ToolAbortError/ToolError and is not explained by an aborted signal is wrapped as ToolError(`Shared browser attach failed: <message>`). It preserves the underlying message (from puppeteer.connect, broker RPC, etc.) while giving it a uniform tool-level shape.

Source

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

			protocolTimeout: BROWSER_PROTOCOL_TIMEOUT_MS,
		});
		// Attaching to the shared daemon is the natural point to sweep targets
		// left behind by omp processes that died without teardown — bounds
		// accumulation without a background timer. Best-effort and detached so a
		// slow reap never delays the open (issue #10022).
		void reapOrphanSharedTargets(browser, { projectDir: shared.projectDir, daemonName: shared.daemonName });
		return {
			key: browserKey(kind),
			kind,
			browser,
			sharedDaemon: { name: shared.daemonName, projectDir: shared.projectDir },
			refCount: 0,
			stealth: { browserSession: null, override: null },
		};
	} catch (err) {
		if (err instanceof ToolAbortError || err instanceof ToolError) throw err;
		if (opts.signal?.aborted) throw new ToolAbortError("Browser open aborted");
		throw new ToolError(`Shared browser attach failed: ${err instanceof Error ? err.message : String(err)}`);
	}
}

/** Test-only accessor for the module-global browsers map. */
export function getBrowsersMapForTest(): ReadonlyMap<string, BrowserHandle> {
	return browsers;
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the wrapped <message> in ~/.omp/logs and fix the root cause (usually daemon crash or stale endpoint)
  2. Stop stale daemons (`hub ps`, then stop/kill omp.browser.* daemons) and retry the open
  3. Retry the open — transient races between daemon start and attach usually resolve
  4. Use a dedicated spawned browser instead of the shared daemon if sharing keeps failing
Defensive patterns

Strategy: try-catch

Validate before calling

// confirm the shared endpoint is still alive before attaching
const alive = await fetch(`http://${daemonHost}/json/version`).then(r => r.ok).catch(() => false);
if (!alive) console.warn("Shared daemon endpoint dead; restart it before opening");

Try / catch

try {
  return await openSharedHeadlessHandle(kind, opts);
} catch (err) {
  if (err instanceof ToolError && err.message.startsWith("Shared browser attach failed:")) {
    // log err.message for root cause; restart stale daemons and retry once
  }
  throw err;
}

Prevention

When it happens

Trigger: puppeteer.connect({browserWSEndpoint: shared.wsEndpoint}) throwing (daemon died between start and connect), broker RPC returning unexpected results, race where the daemon exits while refCount bookkeeping runs, or any unexpected exception in the attach path.

Common situations: Shared daemon crashed or was killed (OOM, hub stop) right after start; wsEndpoint stale after daemon restart; concurrent opens racing daemon teardown; transient network/socket errors to the daemon.

Related errors


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