fix(browser): Browser Use cannot connect to IAB from Node REPL on macOS
What issue are you seeing?
Browser Use is installed and the in-app browser is open, but Browser bootstrap fails from the discovered Node REPL tool:
Failed to connect to browser-use backend "iab"
This is on macOS, not Windows/MSIX.
Environment
Codex Desktop: 26.429.20946
codex-cli in app bundle: 0.128.0-alpha.1
Browser Use plugin: openai-bundled/browser-use/0.1.0-alpha1
OS: macOS
Feature state from app-bundled CLI:
apps true
browser_use true
computer_use true
in_app_browser true
plugins true
js_repl removed false
What I verified
The IAB socket exists under:
/tmp/codex-browser-use/*.sock
From normal app-bundled Node, JSON-RPC getInfo with the current session_id and turn_id succeeds and returns:
type: iab
name: Codex In-app Browser
version: 26.429.20946
From mcp__node_repl__js, the same socket connects and writes, but no response is received before timeout. Browser bootstrap then fails with:
Failed to connect to browser-use backend "iab"
Expected behavior
When Browser Use is enabled and the in-app browser is open, the discovered Node REPL tool should be able to initialize Browser Use against the IAB backend.
Notes
This looks different from the Windows helper-binary/path issues. The IAB backend exists and responds from normal Node, but not from the Node REPL tool used by the Browser skill.
18 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
I can reproduce a related macOS failure, with an extra subagent-specific angle.
Environment:
browser_use,in_app_browser,plugins,multi_agent, andtool_searchare enabledRepro:
qa_reviewersubagent.``
js
``const { setupAtlasRuntime } = await import("<browser-use plugin>/scripts/browser-client.mjs");
await setupAtlasRuntime({ globals: globalThis, backend: "iab" });
http://127.0.0.1:51234/README.md.Expected:
The subagent should be able to attach to the Codex in-app browser backend and inspect/navigate the tab.
Actual:
Bootstrap fails before tab creation:
Additional diagnostics:
qa_reviewer, default subagent, and forked-context subagent all failed with the same IAB discovery error.x-codex-turn-metadatasession/turn IDs.nodeRepl.requestMetais getter-only / frozen, and assignment fails with:``
text
``Cannot assign to read only property 'session_id' of object '[object Object]'
``
text
``Browser turn does not belong to this IAB pipe
This looks like the IAB bridge/session ownership is not available consistently to the Node REPL runtime, and subagent sessions appear especially unable to inherit or attach to the parent thread's in-app browser backend.
Adding a more specific repro/signature from another macOS Desktop session. This looks like an IAB route/turn ownership mismatch rather than a missing socket or localhost permission issue.
Environment:
26.429.30905, build2345codex-cli 0.128.0-alpha.1openai-bundled/browser-use/0.1.0-alpha1/tmp/codex-browser-use/*.socksockets exist and are owned by the live Codex app processObserved behavior:
Failure from Browser Use bootstrap in broken threads:
One retry also surfaced the raw ownership error:
Key Desktop app log signature from the thread that worked first and then broke:
Later in the same app process a fresh turn in another thread succeeds because the captured Browser Use route and the Node REPL turn metadata agree:
There is also a second failure mode after the browser pane/guest is torn down:
What I think is happening:
The packaged Desktop app has a strict per-turn IAB ownership check. In the installed app.asar, requireBrowserUseSession rejects if canServeRoute returns false:
And the route lookup is keyed by conversationId + turnId:
So if Desktop captures an IAB route for turn 019df1a1-... but the incoming/synthesized turn or Node REPL Browser Use metadata uses ee9efbe9-..., getInfo throws before returning IAB metadata. The Browser Use client then reports browserCount=0 / iabBrowsers=0 with discovered/get-info/string, even though the socket exists.
Update: local Desktop hotfix tested
Adding a follow-up to my earlier comment: I locally hotpatched the installed Codex Desktop app to test the suspected Browser Use / in-app-browser route mismatch.
What was patched
The patch was applied to the installed Electron bundle:
Inside that ASAR, the changed code lives in the bundled main process JS entry:
The relevant guard is the Browser Use route ownership check,
canServeTurnForBrowserRoute.Before, the check required an exact
{conversationId, turnId}route entry:The local hotfix changed it to tolerate a missing exact per-turn route if there is still an active route for the same
{conversationId, windowId}:Why this matches the observed failure
The failing sessions looked like a turn-id mismatch rather than global Browser Use corruption:
turnId, but later incoming/synthesized Browser Use metadata used anotherturnId.Other IAB routing logs show the backend host keyed by:
So the strict exact-turn check seems narrower than the rest of the Browser Use host routing model.
Safety of the tested change
The hotfix does not remove the conversation/window ownership boundary:
hasActiveTurnRouteForBrowserRoute(t).{conversationId, windowId}.Result after patching
After the hotpatch, both formerly broken conversations started resolving Browser Use routes instead of failing discovery.
For conversation
019df185-2079-7370-9386-6aaf75c2b739:For conversation
019df185-d1d3-7b32-8035-369a6b37fb5f:Post-hotpatch checks in the fresh Desktop logs:
Browser turn does not belong to this IAB pipediscovered/get-info/stringbrowserCount=0/iabBrowsers=0No active Codex browser pane availableLocal Electron packaging note
Because this was a direct local patch to
app.asar, launching the modified app required updating Electron/macOS integrity metadata as well. I'll leave those details out of here.One of these should fix the underlying issue cleanly:
canServeTurnForBrowserRoutetolerate a missing exact{conversationId, turnId}route if there is an active Browser Use route for the same{conversationId, windowId}.turnRoutesso the Browser Use request metadata and route registry use the sameturnId.discovered/get-info/string.And that wasn't enough, the moment more than 1 session uses browser use or makes a turn it errors.
What i did so far - it seems to work for now. running browser_use in 2 sessions in parallel.
Follow-up: local IAB hotfix code changes
I narrowed this down to two related Browser Use / IAB route problems:
(conversationId, turnId)route lookup, but some failing turns captured the route under one turn id and then Browser Use requested the same IAB session under another turn id in the same conversation.I applied a local hotfix to:
The modified bundled files inside the ASAR are:
1. Main process: resolve Browser Use route with same-conversation fallback
File:
Before,
resolveBrowserRoute(...)threw immediately when the exact turn route was missing:After, it first tries the exact route, then falls back to any live route for the same conversation:
2. Main process: allow same-conversation active route when exact turn route is missing
File:
Before:
After:
This keeps the fallback scoped to the same conversation instead of allowing cross-thread ownership.
3. Renderer: capture Browser Use route after
turn/steerFile:
Before:
After:
Verification after patch
Finally, after restarting the app, Browser Use attached successfully through the
iabbackend and basic browser API calls succeeded:There are also other unrelated issues with the IAB not starting up fast enough etc. - but once running this seems to work, so far.
Adding controlled evidence from another macOS Codex Desktop session. This looks like the same IAB route/turn ownership class of failure, with an additional lifecycle symptom: after the IAB route is lost, the in-app browser can remain visually open while Browser Use reports no usable IAB backend or hangs on tab commands.
Environment:
26.429.30905, build234526.3build25D125openai-bundled/browser-use/0.1.0-alpha1iabOriginal failure signature from a private session log, sanitized to avoid workflow/account details:
Later retries in the same broad app state repeated
browsers=0,iabBrowsers=0.Controlled localhost reproduction matrix, avoiding external accounts:
attach_initial_iab: passed. Browser Use attached to a localhost page and could inspect/interact.reattach_after_js_reset: passed. Same IAB tab stayed commandable.js_reset: passed.tabs.new()returnedNo active Codex browser pane available.tabs.list()andtabs.selected()hung until locally bounded by timeouts, andtabs.new()still returnedNo active Codex browser pane available.No Codex IAB backends were discovered, withlistedPipes=18,candidates=20,browsers=0,iabBrowsers=0.Local code observations from the installed app/client:
info.type === "iab"andmetadata.codexSessionIdmatches the currentsession_id.session_idandturn_id.requireBrowserUseSession(), which throwsBrowser turn does not belong to this IAB pipewhen the current turn is rejected by the app-side route check.tabs.list()/tabs.selected()can hang after the backend disappears.discovered/get-info/string, which hides whether each candidate was stale, the wrong session, the wrong type, missing metadata, or rejecting the current turn.Current root-cause hypothesis:
Recommended fixes/regression tests:
Browser turn does not belong to this IAB pipeand afterNo Codex IAB backends were discovered.getInforesult/error, returned type, session match/mismatch, and rejection reason.attach_initial_iab,reattach_after_js_reset,reattach_after_transport_loss,close_last_tab_recovery,turn_mismatch_rebind, anddiscovery_diagnostics.Related fallback issue after this failure class: https://github.com/openai/codex/issues/21068
Adding another macOS Desktop reproduction with a slightly different lifecycle signature: a full local cleanup/restart temporarily restores Browser Use, but the IAB route registry breaks again within a few minutes.
Environment:
Cleanup/restart performed before this repro:
After the restart, the first Browser Use attempt could work briefly. A later attempt against the same local URL failed before navigation with:
The local target was not the problem:
The IAB sockets also existed and were owned by the live Codex process:
Relevant desktop log sequence from the same run:
Interpretation: this does not look like missing socket files, a localhost permission issue, or proxy interference. The sockets are created and held by Codex, but after route/webview lifecycle churn the Browser Use runtime can no longer get a usable IAB backend and all discovered pipe probes time out. The repeated
Received turn/started for unknown conversationentries seem related to the route ownership mismatch described in #21167.Adding another macOS Desktop reproduction after a full Codex App restart. This still fails after restarting the app and retrying the official Browser Use bootstrap.
Environment:
Repro in the Node REPL tool:
Actual result:
The sockets exist and are owned by the live Codex process:
Fresh Desktop log signature from the same post-restart run:
This looks like the IAB/native pipe is present, but the handshake/getInfo path times out. The
workspace_dependenciesfeature sync failure is reproducible immediately after app startup and may be related or at least a useful correlated signature.Adding a related Chrome backend reproduction from macOS Desktop. This is not the IAB backend, but it appears to hit the same Browser Use native-pipe/bootstrap class of failure:
setupAtlasRuntime()can hang before any browser command is issued.Environment:
Initial user request was simply to open GitHub through the Chrome plugin. GitHub itself was reachable from the machine:
Chrome/extension/native host checks all passed:
The normal Chrome plugin bootstrap repeatedly hung until the Node REPL tool call timeout:
Actual result:
Additional controlled checks:
nodeRepl.write('node_repl_ok')succeeds, so the Node REPL transport itself is alive.browser-client.mjsalone succeeds quickly.setupAtlasRuntime().extension-hostprocesses; removed every file under/tmp/codex-browser-use; then started only Google Chrome via the plugin helper./tmp/codex-browser-usecontained exactly one fresh Chrome extension-host socket:Even in that clean state,
setupAtlasRuntime({ globals })still timed out after 45s.As a control, removing the only visible socket so that
/tmp/codex-browser-usewas empty still causedsetupAtlasRuntime()to hang for 15s instead of quickly returning a “no backend discovered” diagnostic. That suggests at least one pre-discovery initialization path can hang independently of the socket scan.Relevant Desktop log signatures from the same session:
Network checks from shell showed:
Hypothesis:
setupAtlasRuntime()should not be able to hang indefinitely during ambient telemetry/account/feature initialization or native-pipe discovery.getInfo.Suggested fixes/regression coverage:
setupAtlasRuntime().connectandgetInfotimeouts for both IAB and extension backends.pre-discovery,pipe-list,pipe-connect,getInfo), and raw error/timeout in diagnostics.this was fixed for me btw by updating codex
Adding a fresh macOS Desktop reproduction from a newer build. This looks related to the Browser Use reliability class here, but the failure is now one layer earlier than IAB route discovery: the sandboxed
node_replkernel cannot start at all.Environment:
Observed original failure:
<redacted-user-home>/Documents/Codex/2026-05-16/debug-and-resolve-why-browser-use<redacted-user-home>/Documents/Codex/2026-05-15/investigate-cloudflare-preview-boot-failure-forAfter fully quitting and reopening Codex:
So the cross-chat stale bridge was cleared.
However, a clean
node_replprobe in the reopened app still fails before any Browser-specific bootstrap:Actual:
Controls:
works:
The standalone
node_replserver also works if launched with the built-in recovery flag:Using a small JSON-RPC harness, that mode can initialize, list tools, and run the same JS successfully:
Additional local signal from the app-bundled sandbox command:
returns:
The installed user config uses legacy sandbox settings, not the new permissions table:
Interpretation:
node_replkernel cannot start under the default sandboxed path.node_repl --disable-sandboxworks, so the issue is specifically sandboxed kernel startup, not the bundled Node binary or JS runtime itself.codex sandbox macos --permissions-profile node_replrepro suggests the runtime may be asking for anode_replpermissions profile that is not defined in legacysandbox_modeconfigs, instead of resolving to a built-in or derived workspace profile.Expected:
node_repldependency should start under the normal sandbox on macOS when the app uses a valid legacy config (sandbox_mode = "workspace-write"), or it should internally map thenode_replruntime to a built-in permissions profile.node_repl --disable-sandbox.node_replprofile is required, the app should generate it, document it, or report a clear actionable error instead of surfacing onlyOperation not permitted.Recommended fix direction:
node_repllauncher/runtime manager, avoid passing--permissions-profile node_replunless that profile exists.:workspacefor the kernel, or derive the profile from the active thread permissions.sandbox_mode = "workspace-write"and no[permissions]table, asserting thatnode_replcan launch a kernel without--disable-sandbox.Adding a regression signature from the current macOS x64 build. The newer Desktop code now uses session routes rather than the older exact turn-route model, but the draft-to-persisted conversation transition still appears to break IAB ownership.
Environment
26.707.31428(build5059)openai-bundled/browser/26.707.31428x86_64This is therefore not a general Intel, TCC permission, page-rendering, or website failure.
Sanitized lifecycle signature
The persisted conversation route is initially created and captured:
Immediately afterward, the sidebar owner sync still reports its draft id even though the owner route already points at the persisted thread:
Desktop then reassigns the route in the opposite direction:
The first
getInfocan complete and create a host keyed by the temporary id, but immediately repeated requests for the persisted session fail:DOM snapshots, Runtime evaluation, screenshots through the IAB backend, and cursor actions then hang or reset the Node runtime even though tab metadata can still be read. The stack points into the packaged Desktop main process:
Suggested fix/regression coverage
client-new-thread:*butownerRoutePathresolves to a persisted/local/<id>, canonicalize ownership to the persisted id.getInfocalls → DOM snapshot → CDP evaluation → cursor/click.This looks like the same general ownership/lifecycle failure class reported earlier in this issue, now reproduced after the session-route refactor.
I’m seeing what appears to be the same general IAB session-binding problem, but I found a deterministic reproduction involving Codex collaboration subagents that I haven’t seen documented here yet. I hope these details help.
Environment
26.707.31428(build5059)26.707.3142826.2(25C56)arm64Reproduction
mcp__node_repl__js:```js
const { setupBrowserRuntime } = await import(
"/Users/<user>/.codex/plugins/cache/openai-bundled/browser/26.707.31428/scripts/browser-client.mjs"
);
await setupBrowserRuntime({ globals: globalThis });
```
``
js
``await agent.browsers.list();
This succeeds and returns the
Codex In-app Browser(type: "iab").``
js
``await agent.browsers.list();
consistently returns:
``
js
``[]
and:
``
js
``await agent.browsers.get("iab");
fails with:
``
text
``Browser is not available: iab
https://www.google.com) to rule out localhost routing.Additional diagnostic finding
Both the root thread and subagent receive valid
x-codex-turn-metadata, includingsession_id,thread_id,turn_id, andthread_source.However, the current bundled
browser-client.mjsderives the browser session binding differently for subagents:IAB candidates are then filtered by comparing that derived value with the backend’s
metadata.codexSessionId.In my session:
This appears to deterministically produce the equivalent of a
no-session-matchresult for every collaboration subagent, even though the same IAB backend is healthy and immediately available to the parent thread.Expected behavior
Either:
At the moment, Browser Use works in the root thread but appears structurally unavailable to all collaboration subagents.
This looks related to the turn/session ownership problems already discussed in this issue, but the deterministic
subagent thread_idversusIAB root session_idmismatch may be a useful additional regression case.I can reproduce this on a newer macOS Desktop build, even while the in-app browser is visibly open and manually usable.
Environment
26.707.51957(build5175)codex-cli 0.144.0-alpha.4openai-bundled/browser-use/0.1.0-alpha126.5.2(build25F84)arm64Reproduction
@Browserto operate the existing tab.mcp__node_repl__jsis available.scripts/browser-client.mjsand run:Exact failure
Expected
Browser Use should bind to the visible in-app browser for the active root thread and expose
agent.browser.tabs.selected().Actual / impact
The NotebookLM page remains visible and manually usable, but Browser Use sees zero browser/IAB backends. This blocked an authorized workflow to add study material and generate a NotebookLM Audio Overview.
This does not appear website-specific because the failure occurs during IAB bootstrap, before any page interaction.
Correction: this Windows-specific reproduction was mistakenly posted under the macOS-focused tracker. The complete report has been moved to #33332: https://github.com/openai/codex/issues/33332
Keeping this short redirect so existing references remain valid. Sorry for the noise.
Additional current macOS evidence for the Browser/IAB route-binding and navigation-timeout class. This is separate from the launchd self-restart problem tracked in #32321.
Environment:
26.707.72221build53070.144.226.707.7222126.5.1build25F80, arm64Observed split behavior:
goto()then hit the fixed outer timeout, including against a tiny test endpoint and a public control page.No ChatGPT browser route is availableCannot find context with specified idroute window is not liveunknown conversationeventsunknown conversationevents were observed in a ten-minute log window. Browser work issued from collaboration subagent conversations was markedly less reliable than root-thread-only browser work.There was also stale ownership on the external Chrome backend: a kernel timeout could terminate the browser-client watcher before
turnEnded/finalize released the tab lease. The extension persistedTAB_LEASESin session storage without an observed TTL/owner-liveness cleanup path. Restarting/reloading the Chrome extension cleared that lease, but it did not fix the IABgoto()timeout.The practical mitigation was:
Suggested areas to inspect:
client-new-thread:*/subagent conversations share the same Browser pane.Related active-pane/window binding report: #20743.
Additional macOS reproduction with a currently active IAB socket but empty backend discovery.
Environment:
26.707.919485440com.openai.codex0.144.526.5.2 (25F84)arm64openai-bundled/browser/26.707.91948Observed result:
The current ChatGPT process creates and actively holds a fresh Unix socket under
/private/tmp/codex-browser-use/, but the browser runtime does not discover the backend. The Node runtime receives currentsession_id,thread_id, andturn_idmetadata.Verified:
browser_use,browser_use_external, andin_app_browserare enabledconfig.tomlcodesign --verify --deep --strictsucceedsaccepted, sourceNotarized Developer IDopenai-internal-testingorbrowser-use/0.1.0-alpha1plugin existsRecovery attempts:
~/.codex/browser/sessionsstate/private/tmp/codex-browser-usesocketsAfter restarting, the app created and held a fresh IAB socket, but no new browser session file was created and discovery still returned an empty list.
The recurring
/private/var/db/DetachedSignaturessystem-log entry does not appear causal: signature, notarization, and Gatekeeper validation all succeed. No IAB crash, signature rejection, or sandbox denial is logged during the fresh startup.This also resembles the empty-backend registration behavior reported in #26470.
Follow-up with a more specific macOS A/B result after updating to Desktop
26.715.21316:The current IAB socket exists and is actively held by the ChatGPT process. A direct Unix-socket connection from the Node REPL kernel fails before any
getInfoor session/turn ownership check is reached:Using the exact same app-bundled Node binary outside the Node REPL Seatbelt context connects successfully to the same live socket:
Current Node REPL turn metadata reports:
This rules out the collaboration-subagent
session_idversus childthread_idmismatch for this reproduction: it is a root/user thread and both identifiers are already identical. The connection is denied before the backend can returnmetadata.codexSessionId.Additional observations:
agent.browsers.list()initially returned[].sandbox: seatbelt.The bundled
node_repl --helpexposes--disable-sandbox, but Desktop rewrites[mcp_servers.node_repl].argsto[]at startup, and the already managed Node REPL process continued to reportsandbox: seatbelt. Therefore this was not usable as a controlled local workaround and was reverted.Likely fix area: permit the managed Node REPL Seatbelt profile to connect to the app-owned
/private/tmp/codex-browser-use/*.sockendpoints, or locate the IAB socket in an IPC path already granted to that profile.