Bug Report: Codex Desktop (Windows) Browser Use — Persistent Statsig Initialization Failure Within a Session
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What version of the Codex App are you using (From “About Codex” dialog)?
ChatGPT Powered by Codex & OWL 版本 26.908.40834
What subscription do you have?
ChatGPT Business PRO
What platform is your computer?
win11
What issue are you seeing?
Bug Report: Codex Desktop (Windows) Browser Use — Persistent Statsig Initialization Failure Within a Session
Summary
Browser Use / Computer Use intermittently and persistently fails within a single Codex Desktop session with:
Unable to load browser request-header policy. Retry the browser command.
and occasionally:
Error: nodeRepl.fetch request failed
Once this failure occurs in a given session/thread, it fails on every subsequent retry within that same session, even after "Retry the browser command," even after the underlying network connectivity is fully healthy and verified. Starting a brand-new Codex session/thread immediately resolves the issue — the very next browser action succeeds in ~2 seconds, with no configuration changes.
This strongly suggests the root cause is a module-level singleton in the Statsig client initialization inside the bundled browser-service.mjs that gets stuck in a failed/incomplete state after a single unlucky initialization attempt (e.g., during a brief network hiccup), and is never retried or reset for the lifetime of that session/thread.
Environment
- OS: Windows 11 Pro x64
- ChatGPT/Codex Desktop version: 26.908.4834.0 (also reproduced on 26.903.9818.0 before an app auto-update)
- Browser plugin build: 26.908.40834 (also reproduced on 26.903.71938)
- Computer Use / Unified Computer Use plugin: same build as above
- Node runtime bundled with Codex: v24.20.0 (
@oai/sky,@oai/cua-repl) [windows].sandbox: reproduced under bothelevatedandunelevated- Local network setup: sing-box (v2rayN) SOCKS/HTTP proxy on
127.0.0.1:10808, later upgraded to full TUN-mode (system-level virtual network adapter, transparent proxying — this fix was not what resolved the issue, see below) - Network context: issue reproduced on both a home Wi-Fi network and a corporate (
DomainAuthenticated) Wi-Fi network
Exact reproduction steps
- Open Codex Desktop, start any task/thread.
- Ask it to use Browser Use / Computer Use to navigate to any URL (e.g.
https://example.com) and read the title. - If the first attempt in that thread happens to hit a transient network condition (see "Suspected trigger" below), the browser tool call fails with:
with a full stack trace ending in:{"apps":[],"browsers":[],"errors":["Browsers: Error: Unable to load browser request-header policy. Retry the browser command."]}Error: Unable to load browser request-header policy. Retry the browser command. at ...\kernel.js:1595:16 at Object.settle (...\worker-runtime.js:396:5) at ...\kernel.js:1871:11 at handleInputFrame (...\worker-runtime.js:418:7) at Socket.<anonymous> (...\worker-runtime.js:434:9) - Every subsequent browser action in the same thread fails identically, even minutes/hours later, even after:
- Retrying multiple times ("Retry the browser command" as instructed by the error itself)
- Fully restarting the Codex Desktop app
- Verifying and fixing all local network/proxy/DNS/firewall/sandbox configuration
- Waiting well past any plausible timeout window
- Opening a brand-new Codex thread and repeating the exact same browser action succeeds immediately (observed: 2.28 seconds wall time for a full
createBrowserTab+getAXState), with zero configuration changes between the failing and succeeding attempt (only ~20 seconds elapsed).
Evidence collected
1. Timeline proving session-scoped persistence, not environment
- Failing attempt (existing thread, created several days earlier): local time
13:35:38–13:37:07, ~20s timeout per call, error as above. - New thread created at local time
13:37:27(20 seconds after the previous failure) — identical browser action, identical machine, identical network state — succeeded in 2.28s and correctly returned:{"title":"Example Domain","url":"https://example.com/"}
This same pattern (fail-forever-in-old-thread / succeed-immediately-in-new-thread) was observed on multiple separate occasions over several days, including across app restarts, network changes (home Wi-Fi → corporate Wi-Fi), and proxy reconfiguration — none of which affected the outcome. Only creating a new thread reliably resolved it.
2. Root cause traced to a module-level singleton
Static analysis of the bundled runtime script plugins/cache/openai-bundled/browser/<version>/scripts/browser-service.mjs shows:
var Gp=1e4, Vr; // Vr is a module-level singleton, never reset
function rw(e){
if (Vr != null) return Vr; // <-- returns the SAME instance forever, even if it failed to initialize
...
return Vr = {
client: new StatsigClient(sdkKey, user, {
networkConfig: { api, sdkExceptionUrl, networkOverrideFunc: (i,s) => e.fetch(i,s) },
...
}),
initialized: false,
...
};
}
async function initStatsig(e) {
let t = rw(e);
try {
if (t.initialization == null) {
t.initialization = t.client.initializeAsync({ timeoutMs: Gp }); // 10s timeout
t.ready = t.initialization;
}
await t.initialization;
await waitReady(t); // another 10s timeout
t.initialized = true;
} catch (r) {
console.warn(r); // swallowed — no retry, no reset of Vr
}
}
async function requireGate(e, gateName) {
let t = rw(e); // ALWAYS returns the same (possibly broken) singleton
if (t?.initialization == null)
throw new Error("Browser request-header policy requires Statsig initialization.");
await t.initialization;
if (!(await waitReady(t))?.success || t.client.loadingStatus !== "Ready")
throw new Error("Unable to load browser request-header policy. Retry the browser command.");
...
}
Key observations:
Vr(the Statsig client + init-state wrapper) is created once per process and cached in a module-level variable. It is never invalidated, retried, or torn down after a failed initialization.- If
t.client.initializeAsync()does not reachloadingStatus === "Ready"within the module's own internal window (two chained 10-second timeouts = ~20s, matching the exact wall-time observed in every failing call),requireGate()throws the "Unable to load browser request-header policy" error — and every future call reuses the same brokenVr, so it throws the identical error every time, forever, for the lifetime of the hosting process. - The only way to obtain a fresh
Vr(and thus a fresh Statsig init attempt) is to get a brand-new JS execution context — which happens automatically when Codex starts a new thread (it spins up a newnode_repl.exe→trusted-worker.jsprocess pair with a fresh temp directory, e.g.%TEMP%\.tmpXXXXXX\), but never happens within an existing thread, no matter how many times the user retries.
3. Ruled-out causes (extensive verification performed)
Over several hours of investigation across multiple failure occurrences, the following were verified as NOT the cause, despite initially appearing plausible:
- DNS poisoning / hijacking on the local network — confirmed present in one instance (plaintext DNS to
ab.chatgpt.comreturned incorrect IPs even against 8.8.8.8; DoH resolution returned correct Cloudflare IPs). This was a real, separate issue and was fixed, but did not fully resolve the recurring failure. - Local HTTP/SOCKS proxy not being honored by
node_repl'senv/env_varsconfig inconfig.toml— confirmed that Codex regenerates and overwrites the entire[mcp_servers.node_repl.env]block on every app start, silently discarding any manually addedHTTPS_PROXY/NODE_USE_ENV_PROXYentries. This is real and reproducible, but not the root cause of this specific bug (see below). - Stale OS-level environment variable propagation (
explorer.execaching an old environment snapshot aftersetx-style updates) — real Windows behavior, fixed by restartingexplorer.exe, but not the root cause here. - Windows-native sandbox (
elevatedmode) network isolation — investigatedcodex_sandbox_offline_block_outbound/..._loopback_tcp/..._loopback_udpfirewall rules scoped to theCodexSandboxOfflinelocal user (SID ending-1003). Confirmed viaGet-CimInstance Win32_Process | Invoke-CimMethod GetOwnerthat none of the actual browser-related processes (node_repl.exe, its childtrusted-worker.jsnode process) run under this restricted identity — they run under the normal user account. Switching[windows].sandboxfromelevatedtounelevateddid not change the failure pattern. - TUIC/QUIC proxy protocol instability on a corporate network — stress-tested with 10 concurrent
fetch()calls toab.chatgpt.comthrough the exact same Node runtime bundled with Codex; all 10 succeeded in under 1 second each, with zero failures, both on a fresh proxy client and hours after prior activity. - General network flakiness — ruled out by the fact that a brand-new thread succeeds in ~2 seconds immediately after an old thread fails, on identical network conditions.
4. Process architecture notes (for engineering triage)
node_repl.exe(parent) spawns a childnode.exe --eval <realpath-shim> <tempdir>\trusted-worker.js <workdir>process per thread/session.trusted-worker.jsdynamicallyimport()s the trustedbrowser-service.mjsmodule referenced byNODE_REPL_TRUSTED_SERVICESin the environment.- The Statsig client inside
browser-service.mjsreceives itsfetchimplementation via a constructor-injectede.fetch, which in the observed build resolves to the global Node.jsfetch(confirmed viakernel.js:if (typeof fetch !== "undefined") runtimeContext.fetch = fetch;). - This global fetch call is a different code path from the higher-level
nodeRepl.fetch(...)API (which instead serializes the request and forwards it via stdout IPC to the parent process using anauthenticated_fetchmessage type, for cookie/auth-aware fetches). The Statsig initialization request does not go through this IPC path — it calls the globalfetchdirectly inside thetrusted-worker.jsprocess.
Suspected trigger for the first failure in a thread
We could not conclusively pin down what causes the first Statsig initializeAsync() call to fail (all manual reproduction attempts with a fresh process succeeded), but candidates include:
- A transient network blip during the ~20s initialization window (e.g., proxy client restart, brief DNS resolution failure, Wi-Fi roaming) that happens to coincide with the very first browser action taken in a new thread.
- Corporate network deep packet inspection / stateful firewall intermittently interfering with the specific TLS/HTTP client fingerprint used by this request, distinct from ordinary
fetch()traffic (unconfirmed, speculative).
Regardless of the trigger, the actual bug is that a single failed Statsig initialization permanently and silently poisons the module-level singleton for the rest of the thread's lifetime, with no retry, backoff, or self-healing logic, and the user-facing error message ("Retry the browser command") is actively misleading because retrying within the same thread can never succeed.
Suggested fix
In browser-service.mjs's Statsig init wrapper:
- On
initializeAsync()failure orloadingStatus !== "Ready"after the internal timeout, invalidateVr(set it back tonull, or store an explicitinitFailedAttimestamp) so the next call torw(e)/requireGate(e, ...)creates a freshStatsigClientand retries initialization, instead of permanently returning the broken cached instance. - Consider adding exponential backoff or at least a short cooldown (e.g. re-attempt after N seconds) rather than an unconditional retry-on-every-call, to avoid hammering the endpoint if it's genuinely down.
- Alternatively/additionally, surface a more actionable error message distinguishing "first-time init failed, will retry automatically" from the current text, which implies a user retry will help when it currently cannot.
Workaround (for other users hitting this)
If Browser Use / Computer Use fails with "Unable to load browser request-header policy" and retrying within the same conversation does not help: start a new Codex thread/conversation and repeat the same browser action there. This has been 100% reliable in our testing (multiple occurrences, always resolved by a new thread, never by retrying in the same thread).
Attachments available on request
- Full firewall connection logs (
pfirewall.log) for the exact failure window, showing healthy proxy traffic (ALLOW TCP 127.0.0.1→127.0.0.1:10808) throughout, with no relevant DROP entries correlating to the failure. - Session JSONL transcripts showing the exact failing tool call, error stack trace, and the immediately-following successful call in a new thread, with millisecond-precision timestamps.
- Extracted source excerpts from
kernel.js,worker-runtime.js,trusted-worker.js,privileged-node-repl.js, andbrowser-service.mjsfrom the temp runtime directory, showing the singleton pattern and IPC architecture described above.
What steps can reproduce the bug?
- Open Codex Desktop, start any task/thread.
- Ask it to use Browser Use / Computer Use to navigate to any URL (e.g.
https://example.com) and read the title. - If the first attempt in that thread happens to hit a transient network condition (see "Suspected trigger" below), the browser tool call fails with:
with a full stack trace ending in:{"apps":[],"browsers":[],"errors":["Browsers: Error: Unable to load browser request-header policy. Retry the browser command."]}Error: Unable to load browser request-header policy. Retry the browser command. at ...\kernel.js:1595:16 at Object.settle (...\worker-runtime.js:396:5) at ...\kernel.js:1871:11 at handleInputFrame (...\worker-runtime.js:418:7) at Socket.<anonymous> (...\worker-runtime.js:434:9) - Every subsequent browser action in the same thread fails identically, even minutes/hours later, even after:
- Retrying multiple times ("Retry the browser command" as instructed by the error itself)
- Fully restarting the Codex Desktop app
- Verifying and fixing all local network/proxy/DNS/firewall/sandbox configuration
- Waiting well past any plausible timeout window
- Opening a brand-new Codex thread and repeating the exact same browser action succeeds immediately (observed: 2.28 seconds wall time for a full
createBrowserTab+getAXState), with zero configuration changes between the failing and succeeding attempt (only ~20 seconds elapsed).
What is the expected behavior?
No response
Additional information
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by examining the bundled browser-service.mjs, especially rw, initStatsig, and requireGate, along with the trusted-worker.js process described in the report. Reproduce the difference between retrying in an existing thread and starting a new thread. Done means a transient Statsig initialization failure can recover on a later browser action within the same thread.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100