I wanted my background coding agent to use the app it was building. Open Chromium, inspect the page, click through a flow, and see whether it actually works.
I got it running with codex app-server, OpenAI’s first-party computer/browser runtimes, and a Linux desktop inside E2B.
Here’s how I put it together, including the auth issues that took me a while to figure out.
My E2B image runs:
Xvfb :99
└─ Openbox
└─ Chromium + OpenAI's Chrome extension
Xvfb provides a virtual screen. Openbox manages the windows. Chromium runs as a desktop application on that screen.
The desktop and tool runtimes share:
DISPLAY=:99
I didn’t need XFCE or the ChatGPT Electron UI. The agent can capture screenshots and interact with the desktop without a human streaming it.
Installing the Codex CLI alone doesn’t give you the computer/browser stack.
I extracted the runtime artifacts from a pinned Linux ChatGPT package:
- the bundled Node runtime and
node_repl - the
chromeandunified-computer-useplugins - the native messaging host
- the first-party skills, including the Linux Sky instructions
I installed the matching Chrome extension separately and preserved its extension ID.
The combination I tested:
| Artifact | Version |
|---|---|
| ChatGPT Linux package | 26.901.51231 |
| Codex CLI | 0.153.4 |
| Chrome extension | 1.26.901.11451 |
I verified the download hashes and checked that the bundled and canonical Codex versions matched.
This is a custom integration using the packaged runtime, not an official standalone installation recipe. I keep the images private; the runtime artifacts are proprietary.
codex app-server
│
├─ cua_repl
│ └─ Linux Sky helper
│ └─ screenshots + keyboard/mouse input
│
└─ node_repl
└─ first-party browser service
└─ native messaging host
└─ Chrome extension
└─ Chromium
Computer Use works through screenshots and operating-system input.
Browser Use provides browser-specific APIs through the extension.
I also installed Playwright CLI as a separate tool. It isn’t Playwright MCP, and it doesn’t replace either first-party runtime.
I registered cua_repl and node_repl as MCP servers in the image’s Codex configuration.
The important settings are:
Computer Use:
DISPLAY=:99
CUA_REPL_ENABLED_SURFACES=computer
Browser Use:
DISPLAY=:99
BROWSER_USE_AVAILABLE_BACKENDS=chrome
NODE_REPL_TRUSTED_SERVICES={"browser":"<plugin path>/scripts/browser-service.mjs"}
I kept the complete plugin directories at stable image-owned paths and copied their skills into:
/home/user/.agents/skills/
├─ computer-use/
└─ control-chrome/
The skills matter. They teach Codex how to initialize the runtimes and use the actual APIs.
For Computer Use, initialization happens inside cua_repl:
await cua.getState();
Then, in a later call in the same kernel:
var { sky } = await import("@oai/sky");
await nodeRepl.emitImage(
(await sky.get_screenshot())[0].data_url
);
For Browser Use, the installed skill points to the browser client:
var { setupBrowserRuntime } = await import(
"/opt/coding-agent/image/desktop/plugins/chrome/scripts/browser-client.mjs"
);
var agent = await setupBrowserRuntime();
nodeRepl.write(await agent.browsers.list());
That runs inside the first-party node_repl, not an ordinary Node shell.
One easy mistake: starting a fresh process for every snippet. These tools expect a persistent kernel, so the imported modules and browser handles survive between calls.
Use the installed skill for browser selection and method signatures. Don’t guess the APIs from another computer-use implementation.
This was the part I didn’t expect.
My main Codex process already received credentials through RPC. But Browser Use started a separate native Codex child for authentication and configuration reads.
That child couldn’t inherit the main process’s in-memory login.
The extension connected, but navigation failed with:
Codex auth token is unavailable
I didn’t want to write an auth.json into the sandbox, so I added a per-run Unix-socket broker:
Browser runtime
→ stdio bridge
→ private Unix socket
→ broker
→ real canonical Codex child
The broker initializes the child and authenticates it through RPC before returning the initialization response.
Browser Use can then request auth status and configuration from that child. Credentials still come from my existing control-plane callbacks.
The bridge keeps credentials out of an auth file. Its same-user socket is not a credential-isolation boundary.
Once the child was authenticated, I hit:
unsupported Codex auth method: chatgptAuthTokens
The pinned browser consumer expected "chatgpt". My external RPC login correctly returned "chatgptAuthTokens".
I added a narrow compatibility translation to the browser-facing auth-status response.
The native login stays external. The access token stays unchanged. Before translating the label, I check that the returned token and its account claim match the credentials installed in that child.
This is specific to the versions I tested. It doesn’t change credentials or remote authorization.
Another failure said:
admin-enforced policy could not be verified
The cause was in my bridge.
The native client sent:
{"id":"1","method":"configRequirements/read"}
I had required an explicit "params": null.
Rejecting that valid parameterless request broke the config read. Browser Use reported the failure as a policy-verification error.
Accepting the actual wire format fixed it. I didn’t need to bypass the policy check.
I install the desktop and runtime artifacts during image construction.
A systemd service starts Xvfb, Openbox, and Chromium before the image’s ready marker. E2B captures that running desktop in the snapshot.
Build image
→ install desktop + first-party runtimes
→ write Codex configuration
→ start desktop
→ capture ready snapshot
Create sandbox
→ attach to the existing desktop
→ authenticate Codex through RPC
→ run computer/browser tools
/resume preserves the desktop rather than rebuilding it for every prompt.
One detail worth remembering: changing the image only affects new sandboxes. Existing ones keep their image-baked tools.
That lets my background agent run its coding work and use Chromium in the same sandbox, through the same Codex session.