Browser/Chrome plugin setupBrowserRuntime invalidates existing tab handles on repeated setup
What version of the Codex App are you using?
26.602.40724
What platform is your computer?
Windows 11 x64
What issue are you seeing?
The bundled Browser/Chrome browser-use client can invalidate an already claimed tab handle when setupBrowserRuntime({ globals }) is called a second time in the same JavaScript kernel/session.
Observed behavior:
- Connect to the Chrome extension backend. In this case the backend is a real Brave/Chromium browser extension backend.
- List user tabs with
browser.user.openTabs(). - Claim an existing user tab with
browser.user.claimTab(tabInfo). - Verify the returned
Tabhandle works withtab.title()/tab.url(). - Call
setupBrowserRuntime({ globals })again in the same kernel. - Try to use the original
Tabhandle again.
Before the local patch, the old handle failed with:
native pipe is closed
In earlier runs, a similar stale-handle failure appeared as:
Tab not found ... Existing tabs: none
The real browser tab was still open and could be found again via browser.user.openTabs(), so this was not a user tab being closed. It appears to be the browser-use runtime disposing/recreating the browser connection during repeated setup, which changes the backend id and invalidates handles from the previous connection.
What steps can reproduce the bug?
Use the bundled browser client from the Codex Browser/Chrome plugin and run this sequence:
const { setupBrowserRuntime } = await import('<plugin root>/scripts/browser-client.mjs');
await setupBrowserRuntime({ globals: globalThis });
const backends1 = await agent.browsers.list();
const extension1 = backends1.find((b) => b.type === 'extension');
const browser1 = await agent.browsers.get(extension1.id);
const tabInfo = (await browser1.user.openTabs()).find((t) => t.url && !t.url.startsWith('chrome://'));
const tab = await browser1.user.claimTab(tabInfo);
await tab.title(); // works
await setupBrowserRuntime({ globals: globalThis });
await tab.title(); // fails before patch: native pipe is closed
What is the expected behavior?
Calling setupBrowserRuntime({ globals }) repeatedly in the same live JavaScript kernel should be idempotent, or at least should not dispose an existing healthy browser-use runtime and invalidate live Tab handles.
If the existing connection is healthy, setup should reuse it. If the connection is dead, setup can rebuild it.
Additional information
A local patch that fixed the repro was to add an idempotency marker and health check in setupBrowserRuntime:
- after assigning
globals.agentandglobals.display, mark the runtime as initialized for the current module URL; - on subsequent calls, if the marker matches and
agent/displayexist, first run a cheap health check such asawait globals.agent.browsers.list(); - if the health check passes, return without disposing/recreating browser connections;
- if the health check fails, clear the marker and rebuild normally.
After that local patch, the repro passed:
- backend id remained stable across the second setup call;
- the original claimed
Tabhandle still worked; - the claimed Brave tab stayed controllable after an additional wait;
- cleanup with
browser.tabs.finalize({ keep: [{ tab, status: "handoff" }] })still worked.
This is especially noticeable in workflows where agents bootstrap the browser runtime more than once while continuing an unfinished real-browser task, because the page remains open but the agent loses its handle unexpectedly.