codex - 💡(How to fix) Fix ~40s hang on startup (Windows)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

On every cold launch of the Codex Desktop app on Windows, the UI is interactive but unresponsive — every renderer-to-backend RPC stalls for ~42 seconds before all completing simultaneously. Root cause is an unbounded Promise.all fan-out in the desktop's git-origins worker, which spawns ~3 × N git.exe processes (where N = recent workspaces). With Microsoft Defender real-time scan and Windows process-creation overhead, this dominates wall-clock time on launch and blocks the Electron main process's stdio reader, so unrelated backend RPCs queue behind it.

Error Message

error Request failed … error={"code":-32600,

Root Cause

References below are to the formatted bundle from app.asar (resources\app.asar/.vite/build/worker.js and main-sqI8jfJr.js).

Fix Action

Fix / Workaround

  1. Renderer invalidates the git-origins React-Query key on active-workspace-roots-updated / workspace-root-options-updated events fired during launch.
  2. Main process IPC handler at main-sqI8jfJr.js:21042"git-origins": async ({ dirs, hostId }) => {…}. When the renderer passes no explicit dirs, it falls back to globalState recent workspaces (all 8). Forwards to the per-host worker via this.requestGitWorker({ method: 'git-origins', params: { dirs, … } }).
  3. Worker dispatcher at worker.js:59056case 'git-origins': calls j6(dirs, gitManager, signal) then emits the [git-origins] worker-complete log line with elapsedMs.
  4. j6M6w6 (worker.js:55787, 55790, 55756):

Workarounds (user-side, until fixed)

Code Example

2026-05-17T16:00:31.718Z info  [git] [git-origins] worker-complete dirCount=8 elapsedMs=41981 hostId=local originCount=8
2026-05-17T16:00:27.270Z info  [AppServerConnection] response_routed … durationMs=45811 method=config/read
2026-05-17T16:00:27.271Z info  [AppServerConnection] response_routed … durationMs=45789 method=experimentalFeature/enablement/set errorCode=-32600
2026-05-17T16:00:27.646Z info  [AppServerConnection] response_routed … durationMs=42737 method=account/read
2026-05-17T16:00:27.647Z info  [AppServerConnection] response_routed … durationMs=42745 method=skills/list
2026-05-17T16:00:27.648Z info  [AppServerConnection] response_routed … durationMs=42737 method=config/read
2026-05-17T16:00:27.649Z info  [AppServerConnection] response_routed … durationMs=42738 method=windowsSandbox/readiness
2026-05-17T16:00:27.649Z info  [AppServerConnection] response_routed … durationMs=42736 method=hooks/list
2026-05-17T16:00:27.668Z info  [AppServerConnection] response_routed … durationMs=42884 method=model/list
2026-05-17T15:59:44.902Z info  [electron-message-handler] Skills/list request cwdsCount=8 forceReload=false

---

<workspaces>\repo-a                          (git)
<workspaces>\parent-repo\subdir              (no .git; subfolder of a repo)
<workspaces>\repo-b                          (git)
<workspaces>\repo-c                          (git)
<workspaces>\repo-d                          (git)
<workspaces>\repo-e                          (git)
<workspaces>\repo-f                          (git)
C:\Users\<user>\Downloads                    (no .git)

---

async function w6(e, t, n) {
  return Promise.all(
    e.map(async (e) => {
      try { return await T6(nW(e), t, n); }
      catch (t) { /* swallow, return null origin */ }
    }),
  );
}

---

let i = (await t.getWorktreeRepository(r, n))?.root ?? tW(e),
    a = await t.getRepoRepository(r, n),
    o = await a?.getOriginUrl(),
    s = a?.getCommonDir() ?? null;

---

durationMs=45811 method=config/read
durationMs=42738 method=windowsSandbox/readiness
durationMs=42745 method=skills/list
durationMs=42884 method=model/list

---

warning Failed to load shell env caller=startup detail="Timed out after 5000ms." durationMs=5085

---

error Request failed … error={"code":-32600,
  "message":"unsupported feature enablement `auth_elicitation`:
   currently supported features are apps, memories, mentions_v2, plugins,
   remote_control, tool_search, tool_suggest, tool_call_mcp_elicitation"}
  method=experimentalFeature/enablement/set

---

// pseudo-test
const dirs = recentWorkspaces();        // 8+ Windows paths
const t0 = Date.now();
await scanGitOrigins(dirs);              // freshly seeded worker (no cache)
assert(Date.now() - t0 < 5000);          // budget: 5 s on Windows w/ Defender
RAW_BUFFERClick to expand / collapse

What version of the Codex App are you using (From “About Codex” dialog)?

26.513.40821

What subscription do you have?

Enterprise

What platform is your computer?

Microsoft Windows NT 10.0.26100.0 x64

What issue are you seeing?

Codex not responding for about 40s any time I start it.

https://github.com/user-attachments/assets/6314a8f8-cb90-48d6-a976-090a176cf95f

What steps can reproduce the bug?

Start the app

What is the expected behavior?

It should let me use it in a reasonable amount of time after I open it.

Additional information

<details> <summary>Agentic Analysis</summary>

Bug report: Codex Desktop locks up for ~42 seconds on cold launch (Windows)

Summary

On every cold launch of the Codex Desktop app on Windows, the UI is interactive but unresponsive — every renderer-to-backend RPC stalls for ~42 seconds before all completing simultaneously. Root cause is an unbounded Promise.all fan-out in the desktop's git-origins worker, which spawns ~3 × N git.exe processes (where N = recent workspaces). With Microsoft Defender real-time scan and Windows process-creation overhead, this dominates wall-clock time on launch and blocks the Electron main process's stdio reader, so unrelated backend RPCs queue behind it.

TL;DR

  • Observed: ~30–40 s "locked" feel on app start.
  • Measured: ~42 s RPC stall on every UI bootstrap call (config/read, account/read, skills/list, model/list, hooks/list, windowsSandbox/readiness, …).
  • Cause: worker.js w6() does Promise.all(dirs.map(...)) over recent workspaces, each triggering 3 git.exe spawns. ~24 git invocations race against Defender on cold start.
  • Side cause: heavy worker activity blocks the Electron main process's stdio pipe reader, causing apparently-unrelated backend RPC responses to queue.

Environment

ItemValue
Codex Desktop26.513.4821.0 (MSIX, installed at C:\Program Files\WindowsApps\OpenAI.Codex_26.513.4821.0_x64__<pkgid>\)
Bundled CLI (resources\codex.exe)0.131.0-alpha.9 (per [AppServerConnection] Current reported app-server version)
Recent workspaces8 (from globalState)
Repo dirs scanned6 .git repos under a single workspace root + 2 non-repo dirs (a user Downloads folder, a subfolder of a repo)
OSWindows 11 Enterprise 10.0.26100
GitC:\Program Files\Git\cmd\git.exe (real .exe, not a .cmd shim)
Anti-malwareMicrosoft Defender (enterprise managed)
ShellPowerShell 7 (pwsh), profile on OneDrive
Remote SSH connections configured5 (4 × remote SSH hosts, 1 × WSL)

Steps to reproduce

  1. Have a Codex Desktop install on Windows with several recent workspaces (≥ 4 git repos works).
  2. Close all Codex windows. Wait long enough that the b6 repo cache TTL (u6 = 24h) has expired — i.e. relaunching at least a day later, or after a reboot, or after killing all Codex.exe processes.
  3. Launch Codex. Time the period from app window appearance to UI becoming responsive (e.g., clicking the model picker returns data).

Expected: a few seconds. Actual: ~30–45 seconds of unresponsiveness.

Evidence from logs

Logs in %LOCALAPPDATA%\Packages\OpenAI.Codex_<id>\LocalCache\Local\Codex\Logs\…\codex-desktop-<sessionGuid>-<pid>-tN-iN-HHMMSS-0.log.

Timeline of one cold launch (process start at 15:59:25.440)

t (s)Event
0.0Launching app … platform=win32 packaged=true
5.1warning Failed to load shell env caller=startup detail="Timed out after 5000ms."
7.1[AppServerConnection] stdio_transport_spawned … codex.exe pid=30336
7.5initialize_handshake_result durationMs=373 outcome=success (backend healthy)
8.6[startup][renderer] app routes mounted after 8634ms (UI mounted)
13.5First batch of backend RPCs return in ~3–4 s — normal.
~24git-origins worker-complete dirCount=2 elapsedMs=1119 hostId=local originCount=2 (first batch of 2 dirs — fast)
~24git-origins worker-complete dirCount=1 elapsedMs=1065 hostId=local originCount=1
66.3git-origins worker-complete dirCount=8 elapsedMs=41981 hostId=local originCount=842 s stall
~66All pending RPCs return at once: config/read durationMs=45811, account/read durationMs=42737, skills/list durationMs=42745, model/list durationMs=42884, hooks/list durationMs=42736, windowsSandbox/readiness durationMs=42738, …

Notable: every blocked RPC completes within the same 30 ms window once the git scan finishes. This is the giveaway that they were queued in the Electron main process's stdio reader, not actually slow on the backend.

Sample log lines

2026-05-17T16:00:31.718Z info  [git] [git-origins] worker-complete dirCount=8 elapsedMs=41981 hostId=local originCount=8
2026-05-17T16:00:27.270Z info  [AppServerConnection] response_routed … durationMs=45811 method=config/read
2026-05-17T16:00:27.271Z info  [AppServerConnection] response_routed … durationMs=45789 method=experimentalFeature/enablement/set errorCode=-32600
2026-05-17T16:00:27.646Z info  [AppServerConnection] response_routed … durationMs=42737 method=account/read
2026-05-17T16:00:27.647Z info  [AppServerConnection] response_routed … durationMs=42745 method=skills/list
2026-05-17T16:00:27.648Z info  [AppServerConnection] response_routed … durationMs=42737 method=config/read
2026-05-17T16:00:27.649Z info  [AppServerConnection] response_routed … durationMs=42738 method=windowsSandbox/readiness
2026-05-17T16:00:27.649Z info  [AppServerConnection] response_routed … durationMs=42736 method=hooks/list
2026-05-17T16:00:27.668Z info  [AppServerConnection] response_routed … durationMs=42884 method=model/list
2026-05-17T15:59:44.902Z info  [electron-message-handler] Skills/list request cwdsCount=8 forceReload=false

The 8 cwds (extracted from ~/.codex/.codex-global-state.json, paths anonymized):

<workspaces>\repo-a                          (git)
<workspaces>\parent-repo\subdir              (no .git; subfolder of a repo)
<workspaces>\repo-b                          (git)
<workspaces>\repo-c                          (git)
<workspaces>\repo-d                          (git)
<workspaces>\repo-e                          (git)
<workspaces>\repo-f                          (git)
C:\Users\<user>\Downloads                    (no .git)

Root cause analysis

References below are to the formatted bundle from app.asar (resources\app.asar/.vite/build/worker.js and main-sqI8jfJr.js).

Call chain

  1. Renderer invalidates the git-origins React-Query key on active-workspace-roots-updated / workspace-root-options-updated events fired during launch.
  2. Main process IPC handler at main-sqI8jfJr.js:21042"git-origins": async ({ dirs, hostId }) => {…}. When the renderer passes no explicit dirs, it falls back to globalState recent workspaces (all 8). Forwards to the per-host worker via this.requestGitWorker({ method: 'git-origins', params: { dirs, … } }).
  3. Worker dispatcher at worker.js:59056case 'git-origins': calls j6(dirs, gitManager, signal) then emits the [git-origins] worker-complete log line with elapsedMs.
  4. j6M6w6 (worker.js:55787, 55790, 55756):
async function w6(e, t, n) {
  return Promise.all(
    e.map(async (e) => {
      try { return await T6(nW(e), t, n); }
      catch (t) { /* swallow, return null origin */ }
    }),
  );
}

This is the unbounded fan-out: no p-limit or semaphore. With 8 dirs that means 8 concurrent T6 chains.

  1. T6 (worker.js:55773) per-dir does:
let i = (await t.getWorktreeRepository(r, n))?.root ?? tW(e),
    a = await t.getRepoRepository(r, n),
    o = await a?.getOriginUrl(),
    s = a?.getCommonDir() ?? null;

Underneath this requires three git.exe invocations per dir:

  • git rev-parse --show-toplevel (via V2)
  • git rev-parse --git-dir (via H3)
  • git config --get remote.origin.url (via d6 at worker.js:55457, wrapping getOriginUrl at worker.js:55646)

Plus one where.exe invocation per worker lifetime to locate git.exe (worker.js:53633 I2(), result cached in A2).

  1. Actual spawn in $() at worker.js:53385 via S$({ args, cwd, env, … }).

Why this is so slow on Windows

CostApprox
CreateProcess + git.exe + DLL load80–150 ms
Defender real-time scan of git.exe200–400 ms
git config + credential helper init100–200 ms
.git/config + index read50–150 ms
Per git.exe invocation~400–800 ms

8 dirs × 3 spawns = 24 invocations. Even with Promise.all, Defender serializes scans of the same image, and process-creation contention dominates. Empirically: 42 s wall-clock for the batch on this user's machine.

Secondary failure mode: main process backpressure

The Electron main process is single-threaded JavaScript. While the worker child process is producing output (spawning gits, parsing remotes, emitting log events back to main), the main process event loop services that traffic and cannot drain the stdio response pipe from codex.exe (the Rust app-server). Therefore all backend responses queue in the kernel pipe buffer until the worker work clears. This is visible as:

durationMs=45811 method=config/read
durationMs=42738 method=windowsSandbox/readiness
durationMs=42745 method=skills/list
durationMs=42884 method=model/list

— all completing within the same millisecond window even though the backend almost certainly answered them in milliseconds.

Other issues spotted in the same log (lower priority)

1. PowerShell shell-env probe times out hard at 5 s

warning Failed to load shell env caller=startup detail="Timed out after 5000ms." durationMs=5085

PowerShell 7 cold-start on Windows with a typical enterprise profile (oh-my-posh, mise, etc.) commonly exceeds 5 s. Measured on this machine: pwsh -NoProfile ≈ 520 ms, pwsh with profile ≈ 3.6 s — but on a fully cold launch with OneDrive-hosted profile and Defender scanning, it can blow past 5 s. The 5 s cap means Codex falls back to a stripped PATH and user-installed tooling won't be found. Suggest:

  • Raise the cap to 10–15 s on Windows.
  • Or load the env asynchronously and fix up tools later when it arrives, rather than blocking the startup path on it.

2. Protocol drift between desktop and bundled CLI

error Request failed … error={"code":-32600,
  "message":"unsupported feature enablement `auth_elicitation`:
   currently supported features are apps, memories, mentions_v2, plugins,
   remote_control, tool_search, tool_suggest, tool_call_mcp_elicitation"}
  method=experimentalFeature/enablement/set

The desktop (26.513.4821.0) is requesting an experimental feature (auth_elicitation) that the bundled CLI (0.131.0-alpha.9) doesn't know about. Five such calls fail with errorCode=-32600. They don't cause the lockup (the failure is fast — only their response routing is delayed by the same queue), but they're noise that suggests the bundled CLI may be older than the desktop expects.

Suggested fixes (in priority order)

Primary

  1. Bound concurrency in w6's fan-out. Replace the bare Promise.all with a small concurrency limit (2–3). On Windows in particular, paralleling 24 process spawns gains very little because Defender scan-of-same-image serializes; meanwhile it starves the main process event loop. p-limit 2 should give nearly the same wall-clock with far less main-process pressure.

  2. Persist a (dir → originUrl, root, commonDir) cache to disk keyed by dir path and the mtime of .git/config. On cold launch, render recents from the disk cache and refresh in the background. The in-process memoize (maxAge: 24h at worker.js:55648) is useless across process restarts.

  3. Defer the entire git-origins scan until after first paint, or until the user opens the workspace picker. Recents can show their last known origin and refresh lazily.

  4. Filter the input list before scanning. Skip dirs that don't contain (or sit inside) a .git directory. The current input includes Downloads and a non-repo subfolder. Doing a cheap fs.access(dir/.git) first would let the scan early-exit on those without spawning git.

  5. Move backend stdio reads off the same loop slot as worker IPC. Either give the AppServer transport a dedicated IO thread/worker, or setImmediate-yield more aggressively while processing worker traffic, so other RPC traffic doesn't queue behind it. Even with (1)–(4), heavy workspaces will sometimes do real work; the desktop should not let that block all backend RPC traffic.

Secondary

  1. Increase the shell-env probe timeout on Windows to 10–15 s, or make it async and non-blocking on startup.

  2. Ship a matched desktop/CLI pair, or have the desktop gracefully no-op on features the CLI doesn't advertise (no errorCode=-32600).

Workarounds (user-side, until fixed)

  1. Defender process exclusion for git.exe and where.exe, and ideally the entire Codex install path. Single biggest knob on Windows.
  2. Trim recent workspaces to the 1–2 you actively use.
  3. Make sure git in PATH is a real .exe, not a chocolatey/scoop .cmd shim (otherwise worker.js:53627 P2() wraps every git call in cmd.exe /d /s /c).
  4. Shrink your PowerShell $PROFILE: lazy-load mise activate, oh-my-posh init, etc. Aim for < 2 s.
  5. Leave Codex running between sessions — getOriginUrl's 24 h in-process memoize keeps the cache warm and avoids the cold-launch scan.

Verifying a fix

A simple regression test would replay the same git-origins cold-launch shape:

// pseudo-test
const dirs = recentWorkspaces();        // 8+ Windows paths
const t0 = Date.now();
await scanGitOrigins(dirs);              // freshly seeded worker (no cache)
assert(Date.now() - t0 < 5000);          // budget: 5 s on Windows w/ Defender

Acceptance: cold scan of 8 dirs completes in < 5 s on a Windows machine with Microsoft Defender real-time scanning active and git.exe not on the exclusion list.

Artifacts available

  • Desktop logs: %LOCALAPPDATA%\Packages\OpenAI.Codex_<id>\LocalCache\Local\Codex\Logs\…
  • Global state: ~/.codex/.codex-global-state.json (contains recent workspaces)
  • Extracted+formatted bundle for code references (from app.asar):
    • .vite/build/worker.js
    • .vite/build/main-sqI8jfJr.js
</details>

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

codex - 💡(How to fix) Fix ~40s hang on startup (Windows)