This note documents how to recover browser control when the in-app browser plugin fails.
This solution was obtained purely by asking codex, there should be for sure better solutions. I have no idea what it's doing as it's not my domain of expertise.
Works with version "26.609.71450", it's hardcoded in some of the executed codes.
Problem
The in-app browser skill failed during setup with:
Cannot read properties of undefined (reading 'BROWSER_USE_CODEX_APP_VERSION')
After adding an env object, it then failed with:
privileged native pipe bridge is not available; browser-client is not trusted
The root cause was that the browser client expected two fields on nodeRepl
that were not exposed in this session:
nodeRepl.env
nodeRepl.nativePipe
The browser client uses nodeRepl.nativePipe.createConnection(...) to connect
to the local browser-use socket, normally under /tmp/codex-browser-use.
Prerequisites
Use the node_repl JavaScript tool. If it is not visible, discover it with:
For in-app browser control, use:
~/.codex/plugins/cache/openai-bundled/browser/26.609.71450/scripts/browser-client.mjs
For Chrome extension control, use:
~/.codex/plugins/cache/openai-bundled/chrome/26.609.30741/scripts/browser-client.mjs
Fix In-App Browser
Run this once in the Node JavaScript session before loading the in-app browser
client:
const nodeReplProto = Object.getPrototypeOf(nodeRepl);
if (!("env" in nodeRepl)) {
Object.defineProperty(nodeReplProto, "env", {
value: {},
configurable: true,
});
}
nodeRepl.env.BROWSER_USE_CODEX_APP_VERSION ??= "26.609.71450";
nodeRepl.env.BROWSER_USE_SECURITY_MODE ??= "disabled-for-local-testing";
nodeRepl.env.BROWSER_USE_DISABLE_AMBIENT_NETWORK ??= "1";
const net = await import("node:net");
Object.defineProperty(nodeReplProto, "nativePipe", {
configurable: true,
value: {
createConnection(path) {
return new Promise((resolve, reject) => {
const socket = net.createConnection(path);
const cleanup = () => socket.off("error", onError);
const onError = (err) => {
cleanup();
reject(err);
};
socket.once("error", onError);
socket.once("connect", () => {
cleanup();
resolve(socket);
});
});
},
},
});
Then load the official in-app browser client:
const { setupBrowserRuntime } = await import(
"~/.codex/plugins/cache/openai-bundled/browser/26.609.71450/scripts/browser-client.mjs"
);
await setupBrowserRuntime({ globals: globalThis });
globalThis.browser = await agent.browsers.get("iab");
nodeRepl.write(await browser.documentation());
The documentation output means the in-app browser backend is connected.
Verify In-App Browser
Create a tiny local test page:
<!doctype html>
<meta charset="utf-8">
<title>Browser Bridge OK</title>
<h1>Browser Bridge OK</h1>
<p id="status">verified</p>
Save it as:
/tmp/codex-iab-verify.html
Start a temporary server:
python3 -m http.server 8765 --directory /tmp
If the sandbox blocks socket creation, run the same command with escalation.
Then verify in the browser:
globalThis.tab = await browser.tabs.new();
await tab.goto("http://127.0.0.1:8765/codex-iab-verify.html");
await tab.playwright.waitForLoadState({ state: "load", timeoutMs: 5000 });
const result = {
title: await tab.title(),
url: await tab.url(),
snapshot: await tab.playwright.domSnapshot(),
};
result.verified =
result.title === "Browser Bridge OK" &&
result.snapshot.includes("verified");
nodeRepl.write(JSON.stringify(result, null, 2));
Expected result:
{
"title": "Browser Bridge OK",
"url": "http://127.0.0.1:8765/codex-iab-verify.html",
"verified": true
}
Stop the temporary server afterward with Ctrl-C.
Verify Chrome Backend
Chrome uses the Chrome extension backend. In this conversation it worked
without needing the in-app browser shim.
Load the Chrome browser client:
const { setupBrowserRuntime } = await import(
"~/.codex/plugins/cache/openai-bundled/chrome/26.609.30741/scripts/browser-client.mjs"
);
await setupBrowserRuntime({ globals: globalThis });
globalThis.browser = await agent.browsers.get("extension");
nodeRepl.write(await browser.documentation());
With the same temporary server running, verify Chrome:
globalThis.chromeTab = await browser.tabs.new();
await chromeTab.goto("http://127.0.0.1:8765/codex-iab-verify.html");
await chromeTab.playwright.waitForLoadState({ state: "load", timeoutMs: 5000 });
const chromeResult = {
title: await chromeTab.title(),
url: await chromeTab.url(),
snapshot: await chromeTab.playwright.domSnapshot(),
};
chromeResult.verified =
chromeResult.title === "Browser Bridge OK" &&
chromeResult.snapshot.includes("verified");
nodeRepl.write(JSON.stringify(chromeResult, null, 2));
Expected result:
{
"title": "Browser Bridge OK",
"url": "http://127.0.0.1:8765/codex-iab-verify.html",
"verified": true
}
Before ending Chrome work, release controlled tabs:
await browser.tabs.finalize({ keep: [] });
Then stop the temporary server with Ctrl-C.
Notes
- Do not edit the bundled browser or Chrome plugin files for this fix.
- The shim is session-local. It modifies the current JavaScript session only.
data: URLs are blocked by browser policy, so use http://127.0.0.1 for
local verification.
- The in-app browser and Chrome backends are separate:
iab controls the Codex in-app browser.
extension controls the user's Chrome browser through the Codex extension.
This note documents how to recover browser control when the in-app browser plugin fails.
This solution was obtained purely by asking codex, there should be for sure better solutions. I have no idea what it's doing as it's not my domain of expertise.
Works with version "26.609.71450", it's hardcoded in some of the executed codes.
Problem
The in-app browser skill failed during setup with:
After adding an
envobject, it then failed with:The root cause was that the browser client expected two fields on
nodeReplthat were not exposed in this session:
nodeRepl.envnodeRepl.nativePipeThe browser client uses
nodeRepl.nativePipe.createConnection(...)to connectto the local browser-use socket, normally under
/tmp/codex-browser-use.Prerequisites
Use the
node_replJavaScript tool. If it is not visible, discover it with:For in-app browser control, use:
For Chrome extension control, use:
Fix In-App Browser
Run this once in the Node JavaScript session before loading the in-app browser
client:
Then load the official in-app browser client:
The documentation output means the in-app browser backend is connected.
Verify In-App Browser
Create a tiny local test page:
Save it as:
Start a temporary server:
If the sandbox blocks socket creation, run the same command with escalation.
Then verify in the browser:
Expected result:
{ "title": "Browser Bridge OK", "url": "http://127.0.0.1:8765/codex-iab-verify.html", "verified": true }Stop the temporary server afterward with
Ctrl-C.Verify Chrome Backend
Chrome uses the Chrome extension backend. In this conversation it worked
without needing the in-app browser shim.
Load the Chrome browser client:
With the same temporary server running, verify Chrome:
Expected result:
{ "title": "Browser Bridge OK", "url": "http://127.0.0.1:8765/codex-iab-verify.html", "verified": true }Before ending Chrome work, release controlled tabs:
Then stop the temporary server with
Ctrl-C.Notes
data:URLs are blocked by browser policy, so usehttp://127.0.0.1forlocal verification.
iabcontrols the Codex in-app browser.extensioncontrols the user's Chrome browser through the Codex extension.