Codex Windows app stuck on blank/spinner at startup — unsupported `workspace_dependencies` feature loop

Resolved 💬 4 comments Opened Apr 24, 2026 by jaurund Closed Apr 24, 2026
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

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

Latest version from official installer (MS Store / AppInstaller flow) Exact version not accessible via UI due to startup failure. Logs indicate runtime bundle version: 26.423.10653

What subscription do you have?

ChatGPT Plus

What platform is your computer?

Microsoft Windows NT 10.0.26200.0 x64

What issue are you seeing?

Codex Windows desktop app fails to load the UI and gets stuck on a blank/spinner screen on startup.

Behavior:

  • App launches
  • UI briefly flashes (main interface visible for a split second)
  • Then transitions into a blank screen with spinner
  • App becomes unusable

This occurs consistently on every startup after a recent update.

Logs show a repeated failure loop where the renderer attempts to enable an experimental feature (workspace_dependencies), but the runtime rejects it.

This results in continuous retries and prevents the UI from stabilizing.

This error is persistent across ALL logs from today, across multiple restarts and attempts.

What steps can reproduce the bug?

This is not inherently reproducible yet from my findings, as the bug does not persist across two devices (self-built computer on ASUS Rog strix B550 motherboard, vs Lenovo YOGA laptop)

  1. Update Codex Windows app via official installer (MS Store/AppInstaller downloaded from codex page)
  2. Launch the application
  3. Wait for UI initialization

Observed:

  • UI briefly flashes
  • Then transitions to blank screen with spinner
  • App does not recover

This behavior is consistent across multiple launches.

No specific user interaction is required to trigger the issue.

What is the expected behavior?

Codex app should successfully initialize and display the main UI after startup.

No repeated error loops should occur, and the renderer should not continuously retry failed feature enablement.

The app should remain usable after launch.

Additional information

Relevant log excerpt (repeats continuously across all logs from today):

2026-04-24T19:30:47.843Z info [electron-message-handler] Handled 'ready' message, sent ide-context-updated
2026-04-24T19:30:47.880Z error [electron-message-handler] Request failed conversationId=none durationMs=65 error={"code":-32600,"message":"unsupported feature enablement `workspace_dependencies`: currently supported features are apps, memories, plugins, tool_search, tool_suggest, tool_call_mcp_elicitation"} id=7fa1110e-2520-484a-83a8-0e3d36a51cc3 method=experimentalFeature/enablement/set pendingCountAfter=1 timeoutMs=0
2026-04-24T19:30:47.880Z error [electron-message-handler] Failed to sync experimental feature enablement error={"code":-32600,"message":"unsupported feature enablement `workspace_dependencies`: currently supported features are apps, memories, plugins, tool_search, tool_suggest, tool_call_mcp_elicitation"} hostId=local
2026-04-24T19:30:47.931Z info [AppServerConnection] response_routed broadcastFallback=false conversationId=null durationMs=0 errorCode=-32600 hadInternalHandler=false hadPending=true method=experimentalFeature/enablement/set originWebcontentsId=1 requestId=37b87dd1-ed4f-49c2-b748-80a8b8af282d targetDestroyed=false
2026-04-24T19:30:47.958Z info [electron-message-handler] [startup][renderer] app routes mounted after 665251ms

This loop repeats hundreds of times and generates large log files (~10MB per session).

Troubleshooting attempted:

  • Cleared UI cache (LocalCache\Roaming\Codex)
  • Reset .codex-global-state.json
  • Restarted app multiple times
  • Reinstalled via official installer

No change.

Related issues / possible duplicates:

  • #19279 (similar feature mismatch loop, different platform)
  • #19220 (startup failure with same unsupported feature error)

This appears to be a renderer/runtime feature mismatch, where the desktop app expects support for workspace_dependencies but the runtime does not provide it.

Pasted three most recent logs from startup attempts (yes they are huge, this is a separate issue):
codex-desktop-2ba6edfb-2b1a-4196-a433-33d046f45288-13448-t0-i1-191942-1.log
codex-desktop-2ba6edfb-2b1a-4196-a433-33d046f45288-13448-t0-i1-191942-2.log
codex-desktop-2ba6edfb-2b1a-4196-a433-33d046f45288-13448-t1-i1-191944-0.log

View original on GitHub ↗

4 Comments

github-actions[bot] contributor · 4 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #19415
  • #19220
  • #19279
  • #19295

Powered by Codex Action

jaurund · 4 months ago

After starting Codex CLI, I got an error message of an expired auth_token.

That gave me a hunch.

So I signed in through the codex CLI, and then tried restarting the desktop app. And it worked!

The error from the CLI (de-noised with ChatGPT):

"Provided authentication token is expired. Please try signing in again."
"code": "token_expired"
"status": 401

I then ran:

codex logout
codex login

After signing in again, I restarted the Windows desktop app, and the app successfully loaded.

So the workspace_dependencies loop may have been a secondary symptom. The underlying trigger appears to have been expired authentication causing MCP / app feature synchronization to fail during startup.

After re-authentication, the repeated unsupported feature enablement workspace_dependencies startup loop no longer appears in the latest logs.

Suggested root cause:
Expired auth token → MCP/app sync failure → feature enablement sync fails/retries → desktop UI gets stuck on blank/spinner.

I’m leaving this issue open because the desktop app did not surface an auth-expired error and instead became unusable.

jaurund · 4 months ago

Codex Desktop Chat and Project Recovery Guide

This guide documents a conservative recovery process for Codex Desktop and Codex CLI session context after a failed troubleshooting or reset sequence.

It is based on a Windows recovery where Codex Desktop had previously failed to load after an update, showing a blank/spinner startup loop. The related GitHub issue was:

https://github.com/openai/codex/issues/19437

The issue title was:

Codex Windows app stuck on blank/spinner at startup - unsupported workspace_dependencies feature loop

The recovery goal is to restore chat/session visibility and project/workspace visibility without deleting data permanently.

---

Safety Rules

Use these rules throughout the process:

  1. Do not permanently delete Codex data.
  2. Prefer timestamped backups, renames, and copies over deletion.
  3. Close Codex Desktop before modifying Codex state files.
  4. Do not replace sessions or archived_sessions unless they are actually missing.
  5. Preserve the current broken/new state before restoring from an older backup.
  6. Treat auth.json as sensitive account data.
  7. Do not share raw backups publicly because they can contain chats, project names, paths, tokens, and workspace history.

---

Relevant Data Locations

Use environment variables instead of hard-coded personal paths.

Codex CLI and session data:

$CodexHome = Join-Path $env:USERPROFILE ".codex"

Common files and folders under %USERPROFILE%\.codex:

sessions
archived_sessions
session_index.jsonl
state_5.sqlite
logs_2.sqlite
.codex-global-state.json
auth.json
config.toml

Codex Desktop UI state is usually under the Windows app package cache:

$CodexPackage = Join-Path $env:LOCALAPPDATA "Packages\OpenAI.Codex_2p2nqsd0c76g0"
$CodexUiState = Join-Path $CodexPackage "LocalCache\Roaming\Codex"

Some runtime logs or Electron state may refer to:

$CodexRoaming = Join-Path $env:APPDATA "Codex"

If %APPDATA%\Codex does not exist, that is not necessarily a problem. On some installs the active state is stored under the package LocalCache\Roaming\Codex path.

---

What Each File Does

The most important files for recovery are:

| Path | Purpose | Restore priority |
| --- | --- | --- |
| sessions | Main Codex CLI chat/session JSONL files | High |
| archived_sessions | Archived chat/session JSONL files | High |
| session_index.jsonl | CLI-visible session index used by resume/listing | High |
| .codex-global-state.json | Desktop UI workspace/project/pinned-thread state | High |
| state_5.sqlite | Codex runtime state database | Medium |
| logs_2.sqlite | Codex runtime logs database | Low/diagnostic |
| auth.json | Authentication state | Sensitive |
| config.toml | CLI configuration | Medium |

In the documented recovery, sessions and archived_sessions were intact. The CLI problem was that session_index.jsonl was missing. The Desktop project/sidebar problem was that .codex-global-state.json had been reset to a smaller file missing workspace roots, pinned threads, and project order.

The successful recovery restored only:

session_index.jsonl
.codex-global-state.json

The following were intentionally left untouched:

sessions
archived_sessions
state_5.sqlite
logs_2.sqlite
auth.json
config.toml
Codex Desktop UI cache directory

---

Step 1: Close Running Codex Processes

Before changing files, inspect running processes.

The safest practical approach is:

  1. Close Codex Desktop normally.
  2. Close any extra Codex CLI terminals that are not needed.
  3. If you are using a Codex CLI session to perform recovery, do not stop that specific CLI process unless you have resumed the recovery elsewhere.

Read-only process inspection:

$out = New-Object System.Collections.Generic.List[string]
$rows = New-Object System.Collections.Generic.List[string]
$processes = Try { Get-CimInstance Win32_Process } Catch { @() }
foreach ($p in $processes) {
    $name = [string]$p.Name
    $path = [string]$p.ExecutablePath
    $cmd = [string]$p.CommandLine
    $text = $name + " " + $path + " " + $cmd
    if ($text -match "(?i)(codex|openai|OpenAI\.Codex|\.codex)") {
        $isDesktop = (($path -like "*\WindowsApps\OpenAI.Codex_*\app\Codex.exe") -or ($path -like "*\WindowsApps\OpenAI.Codex_*\app\resources\codex.exe") -or ($cmd -match "(?i)OpenAI\.Codex_.*app-server"))
        $isCli = (($cmd -match "(?i)@openai[/\\]codex") -or ($path -like "*\@openai\codex*"))
        $rows.Add(("{0} PID={1} Desktop={2} CLI={3} Path={4}" -f $name, $p.ProcessId, $isDesktop, $isCli, $path))
    }
}
if ($rows.Count -eq 0) {
    $out.Add("No Codex-related processes found.")
} else {
    foreach ($row in $rows) { $out.Add($row) }
}
[Console]::Out.Write(($out -join "`n"))

If Codex Desktop is still running, close it normally from the window first. If it is stuck, stop only the Codex Desktop process tree, not unrelated apps and not the active recovery CLI session.

---

Step 2: Back Up Relevant Chat and Project Data

Create backups before logout/login, reset, reinstall, or restore operations.

This backs up the main .codex directory and the Desktop UI state directory:

$ErrorActionPreference = "Stop"
$ts = Get-Date -Format "yyyyMMdd_HHmmss"
$home = $env:USERPROFILE
$codex = Join-Path $home ".codex"
$codexBackup = Join-Path $home (".codex_backup_" + $ts)
$package = Join-Path $env:LOCALAPPDATA "Packages\OpenAI.Codex_2p2nqsd0c76g0"
$ui = Join-Path $package "LocalCache\Roaming\Codex"
$uiParent = Join-Path $package "LocalCache\Roaming"
$uiBackup = Join-Path $uiParent ("Codex_UI_BACKUP_" + $ts)
$out = New-Object System.Collections.Generic.List[string]

if ([IO.Directory]::Exists($codex)) {
    Copy-Item -LiteralPath $codex -Destination $codexBackup -Recurse -Force
    $out.Add("Backed up .codex to " + $codexBackup)
} else {
    $out.Add("Skipped .codex backup because it does not exist.")
}

if ([IO.Directory]::Exists($ui)) {
    Copy-Item -LiteralPath $ui -Destination $uiBackup -Recurse -Force
    $out.Add("Backed up Codex UI state to " + $uiBackup)
} else {
    $out.Add("Skipped UI backup because Codex UI state does not exist.")
}

[Console]::Out.Write(($out -join "`n"))

Optional: back up only the most important files into a smaller recovery bundle.

$ErrorActionPreference = "Stop"
$ts = Get-Date -Format "yyyyMMdd_HHmmss"
$home = $env:USERPROFILE
$codex = Join-Path $home ".codex"
$dest = Join-Path $home (".codex_key_files_backup_" + $ts)
$out = New-Object System.Collections.Generic.List[string]
[IO.Directory]::CreateDirectory($dest) | Out-Null

foreach ($name in @("sessions", "archived_sessions")) {
    $source = Join-Path $codex $name
    $target = Join-Path $dest $name
    if ([IO.Directory]::Exists($source)) {
        Copy-Item -LiteralPath $source -Destination $target -Recurse -Force
        $out.Add("Backed up folder " + $name)
    }
}

foreach ($name in @("session_index.jsonl", "state_5.sqlite", "logs_2.sqlite", ".codex-global-state.json", "auth.json", "config.toml")) {
    $source = Join-Path $codex $name
    $target = Join-Path $dest $name
    if ([IO.File]::Exists($source)) {
        Copy-Item -LiteralPath $source -Destination $target -Force
        $out.Add("Backed up file " + $name)
    }
}

[Console]::Out.Write(($out -join "`n"))

Do not upload this backup to GitHub, Discord, issue trackers, or support channels without reviewing and redacting it.

---

Step 3: Log Out and Log Back In to Codex

Use logout/login only after backing up data.

For Codex CLI:

codex logout
codex login

If the app is broken but CLI still works, verify CLI authentication:

codex --version

Then start a new CLI session or resume a known session:

codex resume <session-id>

If codex resume <session-id> works, the chat file still exists and the core CLI session data is recoverable.

If CLI cannot list or find older sessions but codex resume <session-id> works, suspect session_index.jsonl.

---

Step 4: Restart Codex Desktop and Verify

After login or restore:

  1. Fully close Codex Desktop.
  2. Wait a few seconds.
  3. Reopen Codex Desktop.
  4. Check whether the left sidebar shows pinned chats, projects, and recent project chats.
  5. Open a known project.
  6. Confirm a known recovered chat appears under the expected project.
  7. Confirm the model selector loads.

Expected success signs:

Pinned chats are visible.
Projects are visible.
Known project chats are visible.
New chat can be started.
CLI can resume known sessions by ID.

If Desktop opens but projects are gone, do not reset again. Follow the recovery section below.

---

Step 5: Locate Available Backups

Look for full .codex backups:

%USERPROFILE%\.codex_backup_*

Look for Desktop UI backups:

%LOCALAPPDATA%\Packages\OpenAI.Codex_2p2nqsd0c76g0\LocalCache\Roaming\Codex_UI_BACKUP_*

Look for global state backups:

%USERPROFILE%\.codex\.codex-global-state.json.UI_BACKUP_*

Read-only backup inventory:

$out = New-Object System.Collections.Generic.List[string]
$home = $env:USERPROFILE
$package = Join-Path $env:LOCALAPPDATA "Packages\OpenAI.Codex_2p2nqsd0c76g0"
$uiParent = Join-Path $package "LocalCache\Roaming"
$codex = Join-Path $home ".codex"

if ([IO.Directory]::Exists($home)) {
    foreach ($d in [IO.Directory]::EnumerateDirectories($home, ".codex_backup_*", [IO.SearchOption]::TopDirectoryOnly)) {
        $info = [IO.DirectoryInfo]::new($d)
        $out.Add(("DIR {0} LastWrite={1}" -f $d, $info.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")))
    }
}

if ([IO.Directory]::Exists($uiParent)) {
    foreach ($d in [IO.Directory]::EnumerateDirectories($uiParent, "Codex_UI_BACKUP_*", [IO.SearchOption]::TopDirectoryOnly)) {
        $info = [IO.DirectoryInfo]::new($d)
        $out.Add(("DIR {0} LastWrite={1}" -f $d, $info.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")))
    }
}

if ([IO.Directory]::Exists($codex)) {
    foreach ($f in [IO.Directory]::EnumerateFiles($codex, ".codex-global-state.json.UI_BACKUP_*", [IO.SearchOption]::TopDirectoryOnly)) {
        $info = [IO.FileInfo]::new($f)
        $out.Add(("FILE {0} Bytes={1} LastWrite={2}" -f $f, $info.Length, $info.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")))
    }
}

if ($out.Count -eq 0) {
    $out.Add("No Codex backups found.")
}

[Console]::Out.Write(($out -join "`n"))

Do not assume the newest file is correct. Compare size, timestamp, and contents.

---

Step 6: Compare Current State Against Backups

Check whether the current session files are missing or whether only the index/UI state is broken.

Important comparison questions:

  1. Does current .codex\sessions contain the expected JSONL files?
  2. Does current .codex\archived_sessions contain the expected archive files?
  3. Is session_index.jsonl missing, empty, or much smaller than backup?
  4. Is .codex-global-state.json much smaller than backup?
  5. Does backup .codex-global-state.json contain keys like electron-saved-workspace-roots, active-workspace-roots, pinned-thread-ids, and project-order?
  6. Are SQLite files locked by a running CLI or Desktop process?

Read-only metadata comparison:

function GetPathMeta($path) {
    if ([IO.Directory]::Exists($path)) {
        $files = 0
        $bytes = [int64]0
        $latest = $null
        foreach ($f in [IO.Directory]::EnumerateFiles($path, "*", [IO.SearchOption]::AllDirectories)) {
            $info = [IO.FileInfo]::new($f)
            $files++
            $bytes += $info.Length
            if ($latest -eq $null -or $info.LastWriteTime -gt $latest.LastWriteTime) {
                $latest = $info
            }
        }
        $latestText = "none"
        if ($latest -ne $null) {
            $latestText = $latest.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss") + " " + $latest.FullName
        }
        return "DIR Files=$files Bytes=$bytes Latest=$latestText"
    }
    if ([IO.File]::Exists($path)) {
        $info = [IO.FileInfo]::new($path)
        return "FILE Bytes=$($info.Length) LastWrite=$($info.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss"))"
    }
    return "MISSING"
}

$home = $env:USERPROFILE
$current = Join-Path $home ".codex"
$backup = Join-Path $home ".codex_backup_<timestamp>"
$out = New-Object System.Collections.Generic.List[string]

foreach ($name in @("sessions", "archived_sessions", "session_index.jsonl", "state_5.sqlite", "logs_2.sqlite", ".codex-global-state.json", "auth.json", "config.toml")) {
    $out.Add(("CURRENT {0}: {1}" -f $name, (GetPathMeta (Join-Path $current $name))))
    $out.Add(("BACKUP  {0}: {1}" -f $name, (GetPathMeta (Join-Path $backup $name))))
}

[Console]::Out.Write(($out -join "`n"))

If current sessions has all backup sessions plus newer sessions, do not replace it.

---

Step 7: If Projects Are Gone in Desktop

If Codex Desktop opens but projects, pinned chats, or workspace roots are gone, the likely file is:

%USERPROFILE%\.codex\.codex-global-state.json

This file can contain:

electron-saved-workspace-roots
active-workspace-roots
pinned-thread-ids
project-order
conversationDetailMode
open-in-target-preferences

Recovery approach:

  1. Close Codex Desktop.
  2. Preserve current .codex-global-state.json.
  3. Restore .codex-global-state.json from a known-good backup.
  4. Restart Codex Desktop.
  5. Verify projects and pinned chats.

Preserve and restore global state:

$ErrorActionPreference = "Stop"
$ts = Get-Date -Format "yyyyMMdd_HHmmss"
$home = $env:USERPROFILE
$codex = Join-Path $home ".codex"
$preserve = Join-Path $home (".codex_restore_preserve_" + $ts)
$currentGlobal = Join-Path $codex ".codex-global-state.json"
$backupGlobal = Join-Path $codex ".codex-global-state.json.UI_BACKUP_<timestamp>"
$out = New-Object System.Collections.Generic.List[string]

[IO.Directory]::CreateDirectory($preserve) | Out-Null

if ([IO.File]::Exists($currentGlobal)) {
    [IO.File]::Copy($currentGlobal, (Join-Path $preserve ".codex-global-state.json.before_restore"), $false)
    $out.Add("Preserved current global state.")
}

if (-not [IO.File]::Exists($backupGlobal)) {
    throw "Backup global state file was not found."
}

[IO.File]::Copy($backupGlobal, $currentGlobal, $true)
$out.Add("Restored .codex-global-state.json from backup.")
$out.Add("Preserve folder: " + $preserve)

[Console]::Out.Write(($out -join "`n"))

If you only have a full .codex_backup_<timestamp> folder, use:

$backupGlobal = Join-Path $env:USERPROFILE ".codex_backup_<timestamp>\.codex-global-state.json"

---

Step 8: If CLI Chats Are Gone or Not Listed

If session files still exist but CLI listing/resume visibility is broken, check:

%USERPROFILE%\.codex\session_index.jsonl

Symptoms of a broken or missing index:

session_index.jsonl is missing.
session_index.jsonl is empty.
session_index.jsonl has fewer records than backup.
codex resume <session-id> works, but old sessions are not listed normally.

Recovery approach:

  1. Preserve current session_index.jsonl if it exists.
  2. Restore session_index.jsonl from full .codex backup.
  3. If current sessions has newer sessions not present in the backup, append index records for those current-only sessions.

The index format is JSONL. Each non-empty line is similar to:

{"id":"<session-id>","thread_name":"<thread-name>","updated_at":"<timestamp>"}

Simple restore when no newer current sessions need to be preserved:

$ErrorActionPreference = "Stop"
$ts = Get-Date -Format "yyyyMMdd_HHmmss"
$home = $env:USERPROFILE
$current = Join-Path $home ".codex"
$backup = Join-Path $home ".codex_backup_<timestamp>"
$preserve = Join-Path $home (".codex_restore_preserve_" + $ts)
$currentIndex = Join-Path $current "session_index.jsonl"
$backupIndex = Join-Path $backup "session_index.jsonl"
$out = New-Object System.Collections.Generic.List[string]

[IO.Directory]::CreateDirectory($preserve) | Out-Null

if ([IO.File]::Exists($currentIndex)) {
    [IO.File]::Copy($currentIndex, (Join-Path $preserve "session_index.jsonl.before_restore"), $false)
    $out.Add("Preserved current session_index.jsonl.")
}

if (-not [IO.File]::Exists($backupIndex)) {
    throw "Backup session_index.jsonl was not found."
}

[IO.File]::Copy($backupIndex, $currentIndex, $true)
$out.Add("Restored session_index.jsonl from backup.")
$out.Add("Preserve folder: " + $preserve)

[Console]::Out.Write(($out -join "`n"))

If current sessions contains sessions newer than the backup, restore the backup index but append records for those current-only sessions. Use thread names that are redacted or generic if the index will be shared.

Example appended records:

{"id":"<new-session-id-1>","thread_name":"Recovered session","updated_at":"<utc-timestamp>"}
{"id":"<new-session-id-2>","thread_name":"Recovery session","updated_at":"<utc-timestamp>"}

---

Step 9: If Session Files Are Actually Missing

If current sessions or archived_sessions is missing files that exist in backup, restore only the missing folders or files.

Avoid replacing a current folder wholesale if it contains newer sessions. Prefer copying missing files from backup into the current folder.

Safe folder merge pattern:

$ErrorActionPreference = "Stop"
$home = $env:USERPROFILE
$currentSessions = Join-Path $home ".codex\sessions"
$backupSessions = Join-Path $home ".codex_backup_<timestamp>\sessions"
$out = New-Object System.Collections.Generic.List[string]

if (-not [IO.Directory]::Exists($backupSessions)) {
    throw "Backup sessions folder was not found."
}

if (-not [IO.Directory]::Exists($currentSessions)) {
    [IO.Directory]::CreateDirectory($currentSessions) | Out-Null
}

foreach ($source in [IO.Directory]::EnumerateFiles($backupSessions, "*.jsonl", [IO.SearchOption]::AllDirectories)) {
    $relative = $source.Substring($backupSessions.Length).TrimStart("\")
    $target = Join-Path $currentSessions $relative
    $targetDir = [IO.Path]::GetDirectoryName($target)
    if (-not [IO.Directory]::Exists($targetDir)) {
        [IO.Directory]::CreateDirectory($targetDir) | Out-Null
    }
    if (-not [IO.File]::Exists($target)) {
        [IO.File]::Copy($source, $target, $false)
        $out.Add("Restored missing session " + $relative)
    }
}

if ($out.Count -eq 0) {
    $out.Add("No missing session files needed restore.")
}

[Console]::Out.Write(($out -join "`n"))

Repeat the same idea for:

archived_sessions

---

Step 10: SQLite Files

Do not replace SQLite files first if JSONL session files and global state are enough to recover visibility.

These files may be locked while Codex CLI or Desktop is running:

state_5.sqlite
logs_2.sqlite

Only restore them if:

  1. Codex Desktop is closed.
  2. Codex CLI is closed.
  3. You preserved the current files first.
  4. Restoring session_index.jsonl and .codex-global-state.json did not fix the issue.

If SQLite files are locked, close Codex Desktop and any Codex CLI sessions, then retry. Do not force-copy over locked databases.

---

Step 11: Verification

Verify CLI recovery:

codex resume <known-session-id>

Verify Desktop recovery:

  1. Open Codex Desktop.
  2. Confirm the sidebar shows pinned chats.
  3. Confirm projects are listed.
  4. Confirm known project chats appear.
  5. Open a known recovered chat.
  6. Start a new chat to confirm the app can write new state.

Verify restored files exist:

$home = $env:USERPROFILE
$codex = Join-Path $home ".codex"
$out = New-Object System.Collections.Generic.List[string]

foreach ($name in @("session_index.jsonl", ".codex-global-state.json")) {
    $path = Join-Path $codex $name
    if ([IO.File]::Exists($path)) {
        $info = [IO.FileInfo]::new($path)
        $out.Add(("{0} exists: {1} bytes, LastWrite={2}" -f $name, $info.Length, $info.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")))
    } else {
        $out.Add($name + " is missing.")
    }
}

[Console]::Out.Write(($out -join "`n"))

Expected result after the documented recovery:

session_index.jsonl exists and contains recovered session records.
.codex-global-state.json exists and contains workspace/project UI keys.
sessions and archived_sessions remain untouched.
Codex Desktop shows projects again after restart.
codex resume <session-id> works.

---

Troubleshooting Matrix

| Symptom | Likely cause | First recovery action |
| --- | --- | --- |
| Desktop opens but projects are gone | Reset .codex-global-state.json | Restore .codex-global-state.json from backup |
| CLI can resume by ID but old chats are not listed | Missing or stale session_index.jsonl | Restore or rebuild session_index.jsonl |
| CLI cannot resume a known session ID | Session JSONL may be missing | Check sessions and restore missing JSONL files |
| Desktop blank/spinner after update | App/runtime issue similar to issue #19437 | Back up data, log out/in, restart, then restore UI state if needed |
| SQLite files cannot be copied | Codex process has database lock | Close Desktop and CLI before touching SQLite |
| Restored UI state still does not show projects | Wrong backup or app overwrote state on launch | Close Desktop, compare backup global state, restore again from known-good backup |

---

Notes on Redaction

Before sharing logs, screenshots, or backups, redact:

Windows username
Personal project names
Workspace folder names
Repository names
Absolute local paths
Session IDs if they identify private work
auth.json contents
Tokens, cookies, account identifiers, and API keys

Use placeholders:

%USERPROFILE%
<windows-user>
<project-name>
<repo-name>
<workspace-root>
<session-id>
<timestamp>

Good redacted path examples:

%USERPROFILE%\.codex\sessions
%USERPROFILE%\.codex_backup_<timestamp>
%LOCALAPPDATA%\Packages\OpenAI.Codex_2p2nqsd0c76g0\LocalCache\Roaming\Codex
<workspace-root>\<repo-name>

Avoid publishing unredacted examples like:

C:\Users\<real-user>\...
C:\Users\<real-user>\Projects\<real-project>\...

---

Known-Good Minimal Recovery Pattern

The minimal successful recovery pattern was:

  1. Confirm Codex Desktop was closed.
  2. Leave the active recovery CLI session alone.
  3. Confirm current sessions contained all backup sessions plus newer sessions.
  4. Confirm current archived_sessions matched backup.
  5. Confirm current session_index.jsonl was missing.
  6. Confirm backup session_index.jsonl existed.
  7. Confirm current .codex-global-state.json was reset-looking and missing project/workspace keys.
  8. Confirm backup .codex-global-state.json contained project/workspace keys.
  9. Preserve the current broken global state in a timestamped folder.
  10. Restore session_index.jsonl.
  11. Append index records for current-only sessions.
  12. Restore .codex-global-state.json.
  13. Do not touch session JSONL files, archived session JSONL files, SQLite files, auth, config, or UI cache.
  14. Restart Codex Desktop.
  15. Verify projects and chats reappeared.

This worked because the underlying chat files were still present. The failure was primarily an index/UI-state visibility problem, not a data-loss problem.

michaelvips · 3 months ago

Possible Windows fix: enable Developer Mode for runtime extraction failures

I ran into a similar Codex Desktop runtime installation failure on Windows.

The logs showed repeated primary runtime install failures like:

[primary-runtime-installer] Error installing primary runtime
Failed to extract primary runtime archive:
codex-primary-runtime/dependencies/node/node_modules/.pnpm/@napi-rs+canvas@0.1.100/node_modules/@napi-rs/canvas-win32-x64-msvc:
Can't create ... Invalid argument

[install-primary-runtime] primary_runtime_dependencies_diagnose_finished
bundleVersion=null installed=false problemCount=1