openai / openai/codex

Bug Report: Codex Desktop (Windows) Browser Use — Persistent Statsig Initialization Failure Within a Session

Open
#45,366 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

app browser bug connectivity windows-os
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 both elevated and unelevated
  • 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

  1. Open Codex Desktop, start any task/thread.
  2. Ask it to use Browser Use / Computer Use to navigate to any URL (e.g. https://example.com) and read the title.
  3. If the first attempt in that thread happens to hit a transient network condition (see "Suspected trigger" below), the browser tool call fails with:
    {"apps":[],"browsers":[],"errors":["Browsers: Error: Unable to load browser request-header policy. Retry the browser command."]}
    
    with a full stack trace ending in:
    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)
    
  4. 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
  5. 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:3813: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 reach loadingStatus === "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 broken Vr, 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 new node_repl.exetrusted-worker.js process 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.com returned 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's env/env_vars config in config.toml — confirmed that Codex regenerates and overwrites the entire [mcp_servers.node_repl.env] block on every app start, silently discarding any manually added HTTPS_PROXY/NODE_USE_ENV_PROXY entries. This is real and reproducible, but not the root cause of this specific bug (see below).
  • Stale OS-level environment variable propagation (explorer.exe caching an old environment snapshot after setx-style updates) — real Windows behavior, fixed by restarting explorer.exe, but not the root cause here.
  • Windows-native sandbox (elevated mode) network isolation — investigated codex_sandbox_offline_block_outbound / ..._loopback_tcp / ..._loopback_udp firewall rules scoped to the CodexSandboxOffline local user (SID ending -1003). Confirmed via Get-CimInstance Win32_Process | Invoke-CimMethod GetOwner that none of the actual browser-related processes (node_repl.exe, its child trusted-worker.js node process) run under this restricted identity — they run under the normal user account. Switching [windows].sandbox from elevated to unelevated did not change the failure pattern.
  • TUIC/QUIC proxy protocol instability on a corporate network — stress-tested with 10 concurrent fetch() calls to ab.chatgpt.com through 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 child node.exe --eval <realpath-shim> <tempdir>\trusted-worker.js <workdir> process per thread/session.
  • trusted-worker.js dynamically import()s the trusted browser-service.mjs module referenced by NODE_REPL_TRUSTED_SERVICES in the environment.
  • The Statsig client inside browser-service.mjs receives its fetch implementation via a constructor-injected e.fetch, which in the observed build resolves to the global Node.js fetch (confirmed via kernel.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 an authenticated_fetch message type, for cookie/auth-aware fetches). The Statsig initialization request does not go through this IPC path — it calls the global fetch directly inside the trusted-worker.js process.

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:

  1. On initializeAsync() failure or loadingStatus !== "Ready" after the internal timeout, invalidate Vr (set it back to null, or store an explicit initFailedAt timestamp) so the next call to rw(e) / requireGate(e, ...) creates a fresh StatsigClient and retries initialization, instead of permanently returning the broken cached instance.
  2. 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.
  3. 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, and browser-service.mjs from the temp runtime directory, showing the singleton pattern and IPC architecture described above.
What steps can reproduce the bug?
  1. Open Codex Desktop, start any task/thread.
  2. Ask it to use Browser Use / Computer Use to navigate to any URL (e.g. https://example.com) and read the title.
  3. If the first attempt in that thread happens to hit a transient network condition (see "Suspected trigger" below), the browser tool call fails with:
    {"apps":[],"browsers":[],"errors":["Browsers: Error: Unable to load browser request-header policy. Retry the browser command."]}
    
    with a full stack trace ending in:
    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)
    
  4. 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
  5. 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

Codex-BrowserUse-Bug-Report-2026-09-14.md

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.