Skip to content

[Windows][26.924.2738.0] Startup spinner forever in most launches: renderer root never mounts; restarting child codex.exe fixes it (analysis + workaround) #48896

Open

Description

@kapustiansky

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

26.924.22138 (Microsoft Store package OpenAI.Codex_26.924.2738.0_x64), app-server 0.158.0-alpha.2.1

What subscription do you have?

ChatGPT Plus

What platform is your computer?

Microsoft Windows NT 10.0.26200.0 x64 (Windows 11 Pro 25H2, build 26200.9550)

What issue are you seeing?

After signing in, the app stays on the loading spinner forever in most launches: 8 of 10 signed-in launches in my logs hung, 2 loaded normally. Same symptom as #48558, #48333, #48615, #48364, #48522. I'm filing separately because I have a root-cause analysis from the logs and the bundled code, plus a reliable automated workaround.

The backend is healthy in hung launches:

  • [startup] Host startup critical-path phases completed
  • [AppServerConnection] Codex CLI initialized
  • renderer requests are answered in under 20 ms (config/read, account/read, getAuthStatus, windowsSandbox/readiness, experimentalFeature/list)
  • [statsig-refresh-diagnostics] React root render requested and chatgpt_pubsub_topic_subscribed are logged

But [startup][renderer] app routes mounted never appears, and the renderer goes quiet after that.

What I found (desktop logs plus reading app.asar of 26.924.2738.0):

  1. The spinner is the Suspense fallback debugName: "Startup" that wraps the renderer root (Dio in webview/assets/app-initial-ff48311587c5.js). On desktop startupReady is undefined (electronBridge has no startup), so the boundary is held by a child that suspends and never resolves.
  2. The codex-app-server-connection-changed listener (PRa, which logs remote_connections.connection_state_changed) is inside that boundary. In every hung launch the renderer never logs a connection_state_changed for hostId=local, not even a snapshot. In every successful launch it logs state=connected 1–3 s before routes mount.
  3. The launches that succeed are the slower ones (ms from process start):
launch Codex runtime config selected pubsub_topic_subscribed app routes mounted
ok 7910 12776 12776
ok 6465 12670 12672
hung 6035 10657 never
hung 5666 10013 never
hung 5502 9523 never
hung 5259 9096 never
hung 5227 9089 never
hung 5035 8926 never
hung 4931 8856 never
hung 4711 8402 never

This looks like a startup race: something the root waits for fires before the renderer subscribes to it. Restarting the app-server re-emits the connection events, and the root mounts right away (see the workaround below).

Ruled out (each one tested, the app still hangs): reinstall via winget (msstore source); resetting .codex-global-state.json; moving LocalCache\Roaming\Codex\web aside; clearing ~/.codex/plugins/cache/openai-primary-runtime (3 hung launches right after a fresh plugin install); reboot; network (no proxy, chatgpt.com and persistent.oaistatic.com reachable). Sandboxing is not the cause either. The app hung the same way with the default elevated Windows sandbox, and I switched it off only to rule it out (the logs above are with sandbox_mode = "danger-full-access" and [windows] sandbox = "unelevated"). windowsSandbox/readiness answers in about 10 ms.

What steps can reproduce the bug?

  1. Install 26.924.2738.0 from the Microsoft Store on Windows 11 and sign in.
  2. Quit the app completely (no ChatGPT.exe processes left).
  3. Launch it again. The spinner stays forever in about 80% of launches here, so repeating steps 2–3 reproduces it within a few tries.

What is the expected behavior?

The main window finishes loading within a few seconds on every launch.

Additional information

Reliable workaround: while the spinner is shown, terminate the child codex.exe (the app-server spawned by the main ChatGPT.exe). The app respawns it (app_server_restart_recovery_done in the log) and the UI mounts within about 3 s.

Get-CimInstance Win32_Process -Filter "Name='codex.exe'" | ? { (Get-Process -Id $_.ParentProcessId -EA 0).Name -eq 'ChatGPT' } | % { Stop-Process -Id $_.ProcessId -Force }

I automated it with a launcher that starts or focuses the app, waits up to 15 s for app routes mounted … rendererWindowAppearance=primary in the log of the current main PID, and otherwise restarts the app-server (up to 3 times). It runs from a shortcut: conhost.exe --headless powershell.exe -NoProfile -ExecutionPolicy Bypass -File launch-chatgpt.ps1. Tested on a cold start (UI up in about 20–30 s) and on an already-running app (no-op).

launch-chatgpt.ps1
$app = 'shell:AppsFolder\OpenAI.Codex_2p2nqsd0c76g0!App'
$logRoot = Join-Path $env:LOCALAPPDATA 'Packages\OpenAI.Codex_2p2nqsd0c76g0\LocalCache\Local\Codex\Logs'
$mountTimeoutSec = 15
$maxRestarts = 3

function Get-AppMain {
    Get-CimInstance Win32_Process -Filter "Name='ChatGPT.exe'" |
        Where-Object { $_.ExecutablePath -match '\\OpenAI\.Codex_' -and $_.CommandLine -notmatch '--type=' } |
        Select-Object -First 1
}

function Test-Mounted($mainPid) {
    $log = Get-ChildItem -LiteralPath $logRoot -Recurse -Filter "*-$mainPid-t0-*.log" -ErrorAction SilentlyContinue |
        Sort-Object CreationTime -Descending | Select-Object -First 1
    $log -and (Select-String -LiteralPath $log.FullName -Pattern 'app routes mounted.*rendererWindowAppearance=primary' -Quiet)
}

Start-Process $app   # launches the app, or focuses it if it is already running

$main = $null
for ($i = 0; $i -lt 30 -and -not $main; $i++) { Start-Sleep -Milliseconds 500; $main = Get-AppMain }
if (-not $main) { return }

for ($restart = 0; $restart -le $maxRestarts; $restart++) {
    $deadline = (Get-Date).AddSeconds($mountTimeoutSec)
    while ((Get-Date) -lt $deadline) {
        if (Test-Mounted $main.ProcessId) { return }
        if (-not (Get-Process -Id $main.ProcessId -ErrorAction SilentlyContinue)) { return }   # app was closed
        Start-Sleep -Seconds 1
    }
    if ($restart -eq $maxRestarts) { return }
    # Still on the spinner: restart the app-server; the app respawns it and the UI proceeds.
    Get-CimInstance Win32_Process -Filter "Name='codex.exe'" |
        Where-Object { $_.ParentProcessId -eq $main.ProcessId } |
        ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
}

Possibly a separate bug, hit on the first sign-in: right after login the primary runtime switched from latest 26.923.10815 to alpha 26.915.20218 and re-downloaded it (about 5 min, while the UI showed the spinner). The post-install plugin sync then failed with failed to back up plugin cache entry: Access is denied. (os error 5) (errorCategory=plugin_cache_windows_file_lock, failureStage=sync_plugins), because the running app holds the plugin files.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    windows-osIssues related to Codex on Windows systems
    app-serverIssues involving app server protocol or interfaces
    on Sep 28, 2026
  2. mumuhylsf-prog commented on Sep 29, 2026

    @mumuhylsf-prog

    Additional observations from Windows 11:

    The startup behavior described here matches what I experienced. When the desktop app was stuck loading, terminating only the child codex.exe process allowed the UI to open. The problem returned after closing and reopening the app.

    I can also reproduce a related freeze after the app has opened. While a local task was running, I selected text and clicked “More details” to open a companion conversation. The panel remained on its loading indicator, and the entire desktop UI stopped responding. Even the task timer stopped updating, and Remote became unavailable. The app remained frozen for at least 3–4 minutes; only the Windows minimize, maximize, and close buttons responded.

    At the time, the frozen UI made it impossible to tell whether the local task was still running. I later observed new files being generated, suggesting that the task continued despite the UI freeze. I did not terminate codex.exe during that active task because doing so might interrupt it.

    The startup hang and this in-session freeze may be related, but I cannot confirm that they have the same root cause.

  3. mumuhylsf-prog commented on Sep 29, 2026

    @mumuhylsf-prog

    This also appears to reproduce on Windows, as reported in #48711. In my case the UI freezes immediately after clicking More Details, before sending anything.

  4. top7845-source commented on Sep 29, 2026

    @top7845-source

    I can now reproduce this consistently.

    Environment:

    • Windows 11 Pro 25H2, build 26200.9550
    • OpenAI.Codex 26.924.2738.0
    • Codex CLI 0.158.0-alpha.2.1
    • OpenAI Support Case: 16015396

    On a clean local profile with no restored old sessions:

    1. Launch Codex Desktop.
    2. The app remains indefinitely on the startup spinner.
    3. Terminating only the child codex.exe process spawned by ChatGPT.exe causes the app-server to respawn.
    4. The UI then loads successfully within a few seconds.
    5. Closing and relaunching the app reproduces the spinner again.
    6. Killing the child codex.exe again restores the UI.

    codex doctor reports 0 failures, successful WebSocket connectivity (HTTP 101 Switching Protocols), healthy local databases, working authentication, and reachable provider endpoints.

    The issue also reproduces after a fresh Windows installation, clean app install, and without restoring any previous .codex chats/session data.

    This appears to match the startup race described in this issue.

  5. hjxccc commented on Sep 29, 2026

    @hjxccc

    Same issue here — adding data points that narrow it down.

    Env: Windows 11 Home 10.0.26100, OpenAI.Codex 26.924.2738.0, app-server 0.158.0-alpha.2.1, ChatGPT sign-in, behind a local HTTP proxy (Clash).

    Frequency: 8 of 15 launches on 2026-09-29 hung. Same signature as this issue: codex-home request hostId=undefined, then no remote_connections.manager_state_set hostId=local, no app routes mounted. Every OK launch logs connection_state_changed ... hostId=local state=connected shortly before mounting.

    Backend is fine in hung launches (~/.codex/logs_2.sqlite): /codex/models 200, remote-plugin sync completes, remote-control websocket reaches Connected. Renderer net breadcrumbs (Sentry scope) are all 200.

    Ruled out (each still hung afterwards):

    • Network/proxy: e.g. a hung launch (07:23 UTC) and an OK launch (07:32 UTC) show the same app-server network behaviour (remote control Connected after ~4 s, no request errors).
    • Stale selected-remote-host-id in .codex-global-state.json (pointed at a remote-control env no longer on the account) — removed, still hung.
    • Remote control: set remote_control_enabled=0 in state_5.sqlite so the websocket doesn't auto-connect at startup — still hung (desktop re-enables it via remoteControl/enable).
    • Global state contents: identical state gave hung at 07:23/07:28 UTC and OK at 07:32/07:33 UTC.

    Code reading agrees with the race described here: AppServerConnection.setConnectionState only broadcasts codex-app-server-connection-changed on an actual state change, and sendConnectionStateSnapshot is sent from registerWebviewWindow at window creation — both can happen before the renderer's listener (inside the Startup Suspense boundary) subscribes. A late subscriber has no way to pull the current state, so it waits forever unless something else changes the state (e.g. restarting the child codex.exe).

  6. tonyfenwick8814121 commented on Sep 30, 2026

    @tonyfenwick8814121

    Additional evidence from a newer Windows build, investigated on 2026-10-01 (UTC+8). This looks related to the startup state-delivery problem, but I cannot confirm an identical root cause.

    Environment

    • Windows x64, OS version/build 10.0.26200.
    • Store package: OpenAI.Codex 26.928.2636.0.
    • Internal desktop app version: 26.928.21956; Electron 42.3.0.
    • Local app-server version after recovery: 0.159.2.
    • The web app was reachable. This does not establish that every desktop network request was healthy.

    Observed failure and recovery

    1. The desktop remained on the animated logo through several relaunch attempts.
    2. Renderer vscode/codex-home startup reads were pending/fetching with no data. Reissuing the exact read-only requests returned real data. Six other stalled local startup reads were also reissued successfully, but the logo remained.
    3. In this case the React root and initialized, idle router already existed. Feature access and onboarding-context reads had completed. The remaining loading gate was gatewayOAuthReadiness: the renderer's local appServerVersion was still null, so its gateway capability/readiness check could not proceed. This was not evidence of a failed gateway sign-in.
    4. Invoking the app's existing local restart message (codex-app-server-restart, hostId: "local", intent: "restart", killCodexProcess: false) caused real initialization state to arrive. The renderer then reported 0.159.2, the logo disappeared, and the sidebar/composer rendered normally. Composer loading and resume-pending flags were false.

    There was no cache wipe, authentication reset, conversation deletion, reinstall, or manual fabrication of readiness data. No draft was submitted as a test.

    Potentially less disruptive recovery mechanism
    The native handler in this build already accepts:

    {"type":"ready","initializationOnly":true}

    Code inspection shows that it replays connection and initialization snapshots to the requesting renderer without restarting the service.

    Update: fresh-launch recurrence on 2026-10-01. The compatibility launcher successfully recovered seven startup reads, but issued zero initialization snapshot requests and expired after three minutes with the logo still present. Inspection again showed the renderer waiting on a null local app-server version. Manually sending the message above to that hung renderer delivered the real version 0.159.2 and restored the sidebar/composer without a service restart. This verifies snapshot replay against an actual recurrence after startup-read recovery; it does not establish that replay alone can repair every cold-start hang.

    The launcher's snapshot trigger depended on locating an internal React scope and observing exactly a null version. I removed that dependency: a persistent logo with missing or temporarily unobservable version state can request the real snapshot; a known valid version suppresses it. Focused regression checks cover unavailable/delayed scope discovery, normal-window no-op behavior, exact read-only query matching, retry caps, and the three-minute lifetime. The exact timing that prevented scope/version detection in the failed run has not been proven.

    I also found a separate launcher bug: after failure, a modal notice retained the recovery mutex indefinitely. The failed helper was still alive roughly twenty minutes later, so subsequent invocations exited as duplicates. The helper now releases the mutex before displaying a notice, and notices close after eight seconds. After stopping only the verified stale helper, the revised launcher successfully connected to the running application, recovered one remaining startup read, and exited with ready, zero errors, no logo and a rendered composer in ten seconds. This running-window check did not interrupt the model service and is not a cold-launch test. These internal message contracts are version-specific and this is a compatibility workaround, not a proven permanent fix.

    A local compatibility launcher limits startup-only retries to three minutes, with at most three retries per exact read-only query/snapshot request. Its already-running/healthy path was verified to perform no repairs. End-to-end cold-launch verification of the revised automatic trigger is still pending; another user task is active, so I am not presenting regression tests or manual recovery as that verification.

    The model service was usable while the renderer was hung. Separate real request telemetry after restart shows transport="responses_websocket" for the previously HTTP-fallback task as well as the diagnostic task. Renderer startup state delivery and model transport fallback therefore need to be diagnosed separately in this occurrence.

    Data-corruption checks
    Read-only SQLite quick_check returned ok for 12 core local databases; the TOML configuration and persisted global-state JSON parsed successfully. Recent inspected logs did not show SQLite corruption, invalid TOML, or disk-full signatures. These checks do not prove that all historical records are semantically valid, but UI recovery without removing those records argues against bulk history deletion as the necessary fix for this occurrence.

    Earlier occurrences on September's 26.908/26.917 builds blocked existing-conversation submission at loading-local-config until the local directory reads were reissued. The newer logo stall additionally required initialization-state recovery.

    Could the desktop subscribe before requesting the initial connection/version snapshot, provide a replay when that state remains missing, and turn stalled startup reads into a bounded, recoverable error instead of an indefinite logo?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appapp-serverIssues involving app server protocol or interfacesbugSomething isn't workingwindows-osIssues related to Codex on Windows systems

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions