Skip to content

Browser Control Skill Fail + Temporal fix #14

Description

@diegomarzaa

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:

node_repl js

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions