Unofficial compatibility layer for using Codex Browser Use / Chrome skill with Chromium on Linux remote hosts.
This project does not redistribute the official Codex desktop app, official Chrome extension, or official Browser Use plugin. It only installs Linux-side compatibility files and small local patches so a Linux Chromium host can satisfy the runtime shape expected by the official Codex Chrome/Browser Use skill.
- Installs a Linux native-messaging bridge for the official Codex Chrome extension.
- Installs a minimal
node_replMCP server exposingjs,js_reset, andbrowser_cleanup. - Exposes
nodeRepland__codexNativePipeso the officialbrowser-client.mjscan connect to Chromium through the native host. - Exposes
nodeRepl.ensureChromiumExtensionReady(), which opens the patched Chromium profile when a fresh Codex conversation reaches Browser setup before any extension-backed browser session is online. It waits for the extension's native-host socket before the Browser skill retriesagent.browsers.get. - When several Chromium extension sockets are alive, prefers the foreground default-profile Chromium socket over headless, incognito, or temporary-profile sockets so logged-in browser state wins backend discovery.
- Launches Linux Chromium with
--password-store=basicso cookie-store startup does not block HTTP/HTTPS page navigation behind a desktop keyring backend. - Provides
nodeRepl.import()and__dynamicImport()for relative imports resolved from the REPL cwd. - Saves emitted browser images to absolute files under the current workspace and prints a Markdown image link, so Codex Desktop can render screenshot deliverables as visible file artifacts.
- Accepts the official Node REPL
jscall shape, including optionaltitleand per-calltimeout_ms/timeoutMsfields. - Adds Browser client CDP call timeouts around page-level operations such as screenshot capture and DOM snapshots, so Chromium-side hangs return a recoverable error instead of stalling the MCP call until the outer tool transport closes.
- Revalidates Browser Use's virtual clipboard in the current document before
paste-backed text input, so repeated navigation in the same tab does not
leave
locator.type()/ paste-style input with a stale clipboard install. - On Linux Chromium, can patch the official extension with a non-CDP
chrome.tabs.captureVisibleTabendpoint. Normal visible-viewport screenshots use that endpoint first, bypassing thePage.captureScreenshotdebugger path that can time out or detach on complex pages. Full-page and cropped screenshots still use CDP. - Bumps the unpacked Chromium extension manifest version when applying that patch, so Chromium re-registers the service worker instead of continuing to run stale cached extension code.
- Keeps a faster CDP visible-viewport fallback that lets Chromium capture the current viewport directly instead of first computing a layout clip rectangle. Screenshot capture remains a supported first-class operation.
- Cleans up native browser sockets after a
jstimeout, so a timed-out browser command is less likely to poison follow-up tool calls in the same MCP process. - Exposes a dedicated
browser_cleanupMCP tool that calls the official Browser Use session finalizer withkeep: [], so agents can close task tabs without running arbitrary cleanup JavaScript.js_resetand normal MCP process shutdown also best-effort run the same cleanup. Long-lived MCP processes also run an idle cleanup timer so forgotten task tabs do not sit in Chromium indefinitely. - Restarts the native host bridge when a client disconnects with an in-flight
browser command, which clears orphaned screenshot/DOM commands that can later
surface as
Detached while handling command. - Adds skill guidance so agents stop retrying the same page-level browser
operation after repeated
tab.playwright/tab.cua/ input timeouts. - Gives each MCP process a unique Browser Use
session_idand stable process-scopedturn_id, matching the isolation the official desktop runtime normally provides for browser-session state. - Patches locally cached Codex Browser Use / Chrome plugin scripts so they recognize Chromium on Linux.
- Patches plugin-local
mcpServersmetadata for both Chrome and Browser Use, so Codex CLI 0.130+ plugin caches expose the selected plugin version's MCP entry instead of depending on a global or stale MCP entry. Chrome keeps the officialnode_replname; Browser Use is renamed tobrowser_node_replto avoid Codex's duplicate plugin MCP server-name de-duplication. - Layers Codex CLI 0.131+ diagnostics into this project's
doctor: it readscodex doctor --json,codex mcp list, andcodex plugin listto verify the effective CLI install, MCP registry, and Browser/Chrome plugin state alongside the Chromium/native-host checks that the upstream doctor does not know about. - On Linux, patches the official
Browser/browser-useskill to keep the@browserentrypoint but select the Chromium-backedextensionbackend. Linux remote hosts do not have the Codex Desktopiabbrowser, and treating the extension asiabis not equivalent for screenshot-heavy flows. The patched bootstrap also asks the local runtime to start Chromium when the extension backend is missing, so a new conversation does not have to remember a manual browser-open step. - Optionally installs macOS Desktop remote path shims under
/Applications/...on the Linux host. This is needed when Codex Desktop on macOS remotely connects to the Linux host and sends its ownnode_replpath throughRefreshMcpServers. - Optionally installs Windows Desktop remote path shims for common Codex and
Codex Beta install paths under
AppData\\Local\\Programs, plus hashedAppData\\Local\\OpenAI\\Codex\\bin\\...paths discovered in app-server logs. This is needed when Codex Desktop on Windows sends a Windows-localnode_repl.execommand to the Linux app-server.
This compatibility layer runs in trusted local mode by default. Browser Use
approval is effectively allow: navigation, browser history access, downloads,
and uploads are not connected to the Codex Desktop approval prompts.
The Linux node_repl runtime intentionally sets:
x-codex-browser-use-security-mode: disabled-for-local-testing
and its createElicitation() helper currently auto-accepts approval requests.
This matches the practical behavior of "Always allow", but it is not the same
as integrating with the Codex Desktop settings page under
Computer use > Google Chrome.
The Codex Desktop approval UI, allowed/blocked domain lists, and per-feature history/download/upload settings are owned by the closed-source desktop app and are not exposed to this Linux remote runtime. Use this project only on hosts, browser profiles, and network environments you trust. A future local policy file could add project-owned allow/deny checks, but it would not be synchronized with the official desktop approval UI.
- Linux host with Node.js 20+.
- Chromium installed as
chromiumorchromium-browser. - The official Codex Chrome extension already installed or loaded in Chromium.
- Codex CLI/app-server already set up on the Linux host.
- A cached official Codex Chrome plugin under
~/.codex/plugins/cache/....
The default extension ID used by this installer is:
hehggadaopoacecdllhhajmbjkdcmajg
Override it with --extension-id if your extension ID differs.
From the project directory on the Linux host:
node bin/codex-browser-use-linux-chromium.js install --patch-chromium-extension --desktop-shims --windows-shims
node bin/codex-browser-use-linux-chromium.js doctorinstall performs these steps:
- Copies runtime files into
~/.local/share/codex-browser-use-linux-chromium. - Writes user-level Chromium and Google Chrome native host manifests.
Use
--system-native-hostwhen Chromium already has a system manifest under/etc/chromium/native-messaging-hostsor Chrome has one under/etc/opt/chrome/native-messaging-hosts. - Patches discovered official Browser Use / Chrome plugin caches and Codex
0.130 staged bundled-marketplace plugin roots under
~/.codex/.tmp/bundled-marketplaces/.... Plugin patching is planned transactionally per plugin root: if any required patch point is missing, that plugin root is left untouched. - Updates or creates plugin-local
.mcp.jsonentries pointing to the installed runtime. Chrome plugin roots usenode_repl; Browser Use plugin roots usebrowser_node_repl. The installer also addsmcpServersmetadata to Chrome and Browser Use plugin manifests when the official cache does not ship it. - Ensures installed bundled plugin caches keep a
latestalias pointing at the installed version directory, so Codex can still discover plugin skills such as@chromeaftercodex plugin addor update operations materialize only a versioned cache path. - With
--patch-chromium-extension, patches the system Chromium Codex extension background script under/usr/share/chromium/extensions/codexso viewport screenshots can usechrome.tabs.captureVisibleTabinstead of the debuggerPage.captureScreenshotpath. Use--extension-root PATHwhen the extension is installed elsewhere. Patched extension instances advertise this capability throughgetInfo(), and the Browser client prefers those instances when old unpatched sockets are still present. The installer also bumps the unpacked extension version, for example from1.1.4to1.1.4.1, to force Chromium to refresh the extension service worker. - Sets
features.tool_search_always_defer_mcp_toolsin~/.codex/config.tomlfor the detected Codex CLI version. Codex 0.130-0.132 needtruesonode_repl/jsis discoverable throughtool_search; Codex 0.133+ works better withfalseon Desktop remote sessions so small MCP tool sets such asnode_repl/jsare exposed directly. Pass--skip-feature-configonly if you want to manage that feature flag yourself. - With
--desktop-shims, creates Linux shims for macOS Codex Desktop remote paths:/Applications/Codex.app/Contents/Resources/node_repl/Applications/Codex.app/Contents/Resources/cua_node/bin/node_repl/Applications/Codex (Beta).app/Contents/Resources/node_repl/Applications/Codex (Beta).app/Contents/Resources/cua_node/bin/node_repl
- With
--windows-shims, creates Linux shims for common Windows Codex Desktop remote paths, including stable and Beta paths underC:\\Users\\<name>\\AppData\\Local\\Programs\\...\\resources\\node_repl.exe. The installer generates both the local Linux username and a capitalized variant; pass--windows-username NAMEwhen the Windows account name differs. It also scans the last 7 days of~/.codex/logs_2.sqlitefor Codex 0.133-style hashed paths such asC:\\Users\\<name>\\AppData\\Local\\OpenAI\\Codex\\bin\\<hash>\\node_repl.exeand creates shims for the exact commands it finds. Backslash-form Windows commands are installed into both~/.local/binand~/.npm-global/bin, so the app-server can find them through PATH. - With
--system-native-host, writes system native host manifests:/etc/chromium/native-messaging-hosts/com.openai.codexextension.json/etc/opt/chrome/native-messaging-hosts/com.openai.codexextension.json
The /Applications shims, system native host manifests, and system Chromium
extension patch require root permissions. The installer preflights passwordless
sudo -n before making changes when a privileged option is requested.
For tests or custom Chromium profile locations, override the manifest root:
node bin/codex-browser-use-linux-chromium.js install --browser-config-root /tmp/browser-configThe installer refuses real writes on non-Linux hosts unless
--allow-non-linux is passed. Use that only for local tests with temporary
directories.
The installer manages this compatibility flag unless --skip-feature-config is
passed:
[features]
tool_search_always_defer_mcp_tools = falseFor Codex 0.133+ through the current tested CLI (0.137.0), false keeps
small MCP tool sets directly callable. This avoids Desktop remote sessions that
can start node_repl but fail to include it in the remote tool_search index.
For Codex 0.130-0.132, the installer keeps the older true behavior because
those versions relied on tool_search for Chrome/Browser skill discovery.
Codex 0.135+ marks the old js_repl feature as removed, so this compatibility
layer should continue to expose node_repl through MCP rather than trying to
restore the removed built-in REPL.
Codex 0.130 also de-duplicates plugin MCP servers by name. If both the Chrome
and Browser Use plugin caches declare node_repl, the first loaded plugin can
claim the name and the other plugin's JS tool may not be discoverable in a
desktop remote conversation. This installer avoids that collision by keeping
Chrome on node_repl and moving Browser Use to browser_node_repl. The patched
Browser skill knows to search for browser_node_repl js when node_repl/js is
not visible.
The patched Chrome skill is deliberately stricter: Chrome tasks should use
node_repl, not browser_node_repl. The latter exists only for Browser /
in-app-browser compatibility and can lead a Chrome request down Browser-specific
routing instructions.
Codex 0.131 keeps the same compatibility requirements. The official CLI now has
codex doctor, codex mcp list, and codex plugin list; this project shells
out to those commands from doctor and reports the upstream status as additional
evidence. A failing upstream terminal check such as TERM=dumb does not by
itself mean the Chromium bridge is broken; use this project's native-host,
extension, socket, and plugin-cache checks for the browser-specific path.
Codex 0.133 keeps those bridge requirements in place. Its codex plugin list
output is marketplace-grouped table output with installed version and plugin
path columns; this project's doctor accepts both the older 0.131/0.132 list
format and the 0.133 table format when reporting Browser Use / Chrome plugin
state. install and patch-plugin also recreate the installed bundled plugin
latest cache alias when Codex has only left a versioned directory such as
chrome/0.1.7; without that alias, @chrome may disappear from skill
discovery even though the Chrome plugin is still installed and enabled.
Codex 0.137 adds codex plugin list --json and --available --json. This
project's doctor prefers that structured output and falls back to the table
output for older Codex builds; the plugin manifest and .mcp.json patch format
remain unchanged.
For direct Codex CLI usage, also add:
node bin/codex-browser-use-linux-chromium.js install --write-codex-configThis appends a marked node_repl MCP block to ~/.codex/config.toml if no
[mcp_servers.node_repl] block exists. If the block already points at a known
older codex-chrome-extension or codex-browser-use-linux-chromium REPL path,
the installer updates it to the current runtime path. Custom node_repl
entries are left unchanged.
Codex Desktop remote sessions may still override MCP config via
RefreshMcpServers; the desktop path shims are what handle that case.
Windows Codex Desktop may send a Windows-local node_repl command path through
RefreshMcpServers, for example a path under AppData\\Local\\Programs or a
hashed OpenAI runtime path under AppData\\Local\\OpenAI\\Codex\\bin or
AppData\\Local\\OpenAI\\Codex\\runtimes\\cua_node. That path varies by
Windows username, install channel, and app update. Install the common Windows
stable/Beta shims and any hashed paths already seen in app-server logs with:
node bin/codex-browser-use-linux-chromium.js install --windows-shims --windows-username YOUR_WINDOWS_USERIf the Windows username matches the Linux username, --windows-username is not
needed. The generated shims cover both backslash commands like:
C:\Users\Josh\AppData\Local\Programs\Codex Beta\resources\node_repl.exe
and forward-slash variants like C:/Users/Josh/.../node_repl.exe. If Codex
Desktop has already sent a hashed command like
C:\\Users\\Josh\\AppData\\Local\\OpenAI\\Codex\\bin\\3c238e29bbc930ff\\node_repl.exe
or
C:\\Users\\Josh\\AppData\\Local\\OpenAI\\Codex\\runtimes\\cua_node\\a89897d3d9baa117\\bin\\node_repl.exe,
the installer discovers it from the last 7 days of ~/.codex/logs_2.sqlite
automatically. If Codex Desktop uses a custom install path that is not in the
logs, inspect the Linux host app-server log for the exact RefreshMcpServers
node_repl.command value and pass it explicitly:
node bin/codex-browser-use-linux-chromium.js install --windows-shims \
--windows-node-repl-path 'C:\Users\Josh\AppData\Local\Programs\Custom Codex\resources\node_repl.exe'Plugin and extension patches are backed up next to each patched file with a
.codex-browser-use-linux-chromium.bak.<timestamp> suffix.
Restore the latest adjacent backups with:
node bin/codex-browser-use-linux-chromium.js restore-plugin
node bin/codex-browser-use-linux-chromium.js restore-extensionIf Chromium keeps reporting the old extension metadata after a file patch, close Chromium and reset its service worker registration cache:
node bin/codex-browser-use-linux-chromium.js reset-extension-cacheUseful logs on the Linux host:
/tmp/codex-native-host-bridge.log
/tmp/codex-node-repl-mcp.log
~/.codex/logs_2.sqlite
If a fresh Codex Desktop conversation cannot see mcp__node_repl__js, run
node bin/codex-browser-use-linux-chromium.js doctor first. The output now
includes the upstream Codex version, official codex mcp list entries for
node_repl and browser_node_repl, and the Browser/Chrome plugin install
state. If those look correct, search
logs_2.sqlite for RefreshMcpServers and verify the exact node_repl.command
exists on the Linux host; on Windows Desktop 0.133+ this may be a hashed
AppData\\Local\\OpenAI\\Codex\\bin\\...\\node_repl.exe command or a
AppData\\Local\\OpenAI\\Codex\\runtimes\\cua_node\\...\\bin\\node_repl.exe
command, so rerun install --windows-shims after the failed attempt to let the
installer create that exact shim. Also search the logs for skipping duplicate plugin MCP server name: if Chrome and Browser Use both declare node_repl, reinstall
this compatibility layer so Browser Use is moved to browser_node_repl, then
restart codex app-server. On Codex 0.130+ remote hosts, verify both
~/.codex/plugins/cache/... and
~/.codex/.tmp/bundled-marketplaces/openai-bundled/plugins/...; the staged
bundled-marketplace copy is what some Desktop remote turns load.
If mcp__node_repl__js appears to hang, check for old
codex-node-repl-mcp.js processes and open connections in
/tmp/codex-native-host-bridge.log. Each MCP process should log a distinct
session_id and a stable turn_id in /tmp/codex-node-repl-mcp.log. The
JavaScript tool has a default 100 second timeout; override it with
CODEX_NODE_REPL_JS_TIMEOUT_MS=0 to disable the timeout or another millisecond
value to tune it. The js tool also accepts per-call timeout_ms/timeoutMs
values, matching the official runtime's call shape. A timeout returns a normal
MCP tool result with isError: true instead of a JSON-RPC transport error, so
the model can read the recovery instruction and continue with follow-up calls
such as js_reset. By default the runtime destroys native browser pipe
connections and resets the JS context after a timeout; set
CODEX_NODE_REPL_RESET_ON_TIMEOUT=0 only if you need to preserve in-memory JS
state across timeouts. Set CODEX_NODE_REPL_EXIT_ON_TIMEOUT=1 only if you
explicitly want timeout errors to terminate the MCP process.
The timeout error includes a consecutive_js_timeouts counter and recovery
hint. domSnapshot() and screenshot capture are normal supported Browser Use
features on Linux Chromium; the compatibility layer should not steer agents to
silently avoid them. With --patch-chromium-extension, normal viewport
screenshots first use chrome.tabs.captureVisibleTab, so they no longer depend
on Page.captureScreenshot. After a js timeout or stale bridge error, do not
run a lightweight tab check such as tab.url() or tab.title() before
re-bootstrap: the runtime resets the JS context by default, so tab is not defined is expected. Run js_reset, re-bootstrap, create a new tab, navigate
to the target URL again, and retry the requested evidence in a fresh
single-purpose call. Do not recover by finding an existing tab with the same URL
after a bridge reset; that tab may still be tied to an orphaned CDP command. If
it still fails after a fresh bootstrap, report that page-level blocker instead
of claiming the screenshot or DOM feature is unavailable.
On Linux Chromium, keep browser bridge calls short and single-purpose. Do not
combine click/fill/keyboard/navigation with domSnapshot(), screenshots, dev
logs, or per-element extraction loops in one js call. Run the interaction,
then verify in a fresh follow-up call. Use screenshots for visual evidence and
domSnapshot() when a full accessibility snapshot is the right evidence; use
supported locator reads such as scoped count(), allTextContents(),
getAttribute(), textContent(), or innerText() only when the task does not
need the full tree. This runtime does not expose evaluate, page.evaluate, or
locator(...).evaluateAll().
browser.tabs.list() and browser.user.openTabs() return info objects, not
controllable Tab handles. Do not call tab.goto() or Playwright/CUA methods
on those list results. Reacquire a current-session tab with
await browser.tabs.get(info.id), or claim a user tab with
await browser.user.claimTab(info), then call goto() on the returned Tab.
agent.browsers is also not an array. It is a registry object with async
list() and get() methods. Use
const browsers = await agent.browsers.list(); before browsers.map(...), or
use await agent.browsers.get("extension") to select Chromium directly.
If multiple extension sockets exist, the node_repl native-pipe shim scores
them before connection. The Chromium process that owns the default profile
singleton wins over headless, incognito, or /tmp user-data-dir sessions. Set
CODEX_BROWSER_USE_CHROMIUM_SOCKET_SELECTION=all only when you explicitly need
to expose every live socket to the Browser client.
If a call fails with native pipe is closed, Detached while handling command, or
Timed out after ... waiting for CDP command, the REPL resets its stale browser
context by default; run js_reset, re-bootstrap, create a new tab, and navigate
to the target URL again before retrying. A follow-up tab is not defined error
means the caller skipped that bootstrap step and tried to use a stale tab
binding.
If Chromium accumulates tabs from Browser/Chrome tasks, use the browser_cleanup
MCP tool on the same node_repl/browser_node_repl server. It runs the Browser
Use tab finalizer for the current session with keep: []; it does not close
arbitrary user tabs. The runtime also best-effort runs that cleanup before
js_reset, when the MCP process exits, and after an idle period in a long-lived
MCP process. If the same JS context already ran browser.tabs.finalize(...)
directly, including a deliberate handoff or deliverable keep list, those
automatic cleanup paths skip the second finalizer call. Set
CODEX_NODE_REPL_CLEANUP_TABS_ON_RESET=0 or
CODEX_NODE_REPL_CLEANUP_TABS_ON_EXIT=0 to disable those automatic cleanup
paths, and tune the cleanup wait with
CODEX_NODE_REPL_BROWSER_CLEANUP_TIMEOUT_MS (default 3000). Tune or disable the
idle cleanup with CODEX_NODE_REPL_IDLE_BROWSER_CLEANUP_MS (default 600000,
set 0 to disable).
If /tmp/codex-native-host-bridge.log contains repeated stdout backpressure
lines, the native host is sending commands to Chromium faster than Chromium is
reading them. This version serializes native-host stdout writes and waits for
drain before sending more frames. You can lower the fail-fast queue cap with
CODEX_NATIVE_HOST_MAX_CHROME_QUEUE_BYTES; the default is 64 MiB.
When a REPL timeout disconnects while a browser command is still pending, the
native host bridge exits by default so Chromium restarts it with a clean command
pipe. This prevents orphaned screenshot or DOM commands from causing later
Detached while handling command failures. Set
CODEX_NATIVE_HOST_EXIT_ON_ORPHANED_PENDING=0 to preserve the old behavior.
The bridge only rewrites JSON-RPC request ids while multiplexing clients; it
passes JSON-RPC responses through unchanged. This is required because the
Chromium extension sends heartbeat requests to the browser client. If response
ids are rewritten, the extension never observes the heartbeat response, assumes
the client is gone, and detaches active browser sessions while page commands
such as domSnapshot() or locator reads are still running.
If a screenshot task says it succeeded but no image appears in the final
assistant message, check whether the final message references
attachment://response_0.png. The screenshot usually did arrive as a previous
tool-output image; that attachment URI is not stable in this compatibility
runtime. The installer patches the Chrome skill to keep the screenshot image in
the latest browser tool output, save it to a workspace file, and avoid synthetic
attachment links. The REPL also replays the most recent emitted image when a
follow-up js call only finalizes browser tabs, covering the common
cleanup-after-screenshot ordering mistake.
doctor also reports Browser Use sockets under /tmp/codex-browser-use, plus
the current upstream Codex CLI doctor/MCP/plugin-list summaries when codex is
available on PATH. Sockets whose owner process is gone are stale; the native
bridge removes stale chromium-<pid>.sock files on startup.
doctor also reports whether the Chromium Codex extension background script and
manifest reload marker have the visible-tab screenshot patch. Use
install --patch-chromium-extension or patch-extension to apply it. If a live
extension still omits codexLinuxCaptureVisibleTab from getInfo(), close
Chromium and run reset-extension-cache, then reopen Chromium. Use
restore-extension to restore the latest backup made by this installer.
See docs/architecture.md for the runtime flow.
