Windows App: WSL switch fails during provisioning, then hides history returned by thread/list

Open 💬 2 comments Opened Jul 14, 2026 by Cameloo1
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Environment

  • Codex App: 26.707.62119
  • MSIX package: OpenAI.Codex_26.707.8479.0_x64
  • Codex CLI/app-server: 0.144.2
  • Windows 11 Home x64, OS 10.0.26200 build 8655
  • WSL 2.6.3.0
  • Ubuntu 26.04 LTS
  • WSL kernel 6.6.87.2-1
  • /usr/bin/bash and bubblewrap 0.11.1 present

Summary

I enabled Run Codex in WSL because native Windows sandbox execution was slow. That native sandbox behavior is already covered by #32314, #31958, and #32168.

The WSL switch exposed a separate Codex App failure:

  1. On the first WSL launch, the App repeatedly terminated the WSL app-server after approximately 90 seconds while it was still performing healthy first-run provisioning.
  2. After provisioning completed, the same app-server initialized in under one second.
  3. Codex App then displayed no existing task history, even though a direct JSON-RPC thread/list request using the App's WSL configuration returned 953 existing threads.
  4. Reverting the App to native Windows immediately restored startup and the original history.

No task data was lost.

Why WSL was enabled

This is context rather than a second bug report:

  • Native elevated mode currently takes approximately 3.5 seconds for a trivial command after workspace cleanup.
  • Simple patch operations take approximately 10-16 seconds.
  • Unelevated mode reduces a trivial command to approximately 0.2 seconds, but patching fails with:

~~~text
windows unelevated restricted-token sandbox cannot enforce split writable root sets directly; refusing to run unsandboxed
~~~

The detailed native sandbox reports are already in #32314, #31958, and #32168. WSL was selected as the practical workaround.

Observed WSL behavior

First launch fails during active provisioning

After enabling Run Codex in WSL and restarting the App, the WSL app-server was terminated after approximately 90 seconds before becoming reachable.

Diagnostics showed that it was not deadlocked or crashed. It was still performing one-time setup work, including:

  • cold-starting WSL;
  • loading the approximately 298 MB executable through /mnt/c;
  • applying approximately 40 SQLite migrations; and
  • backfilling approximately 950 existing threads.

Ubuntu had approximately 13 GB of available memory. There was no OOM, segmentation fault, kernel failure, or executable failure.

This is consistent with a fixed initialization deadline treating active first-run provisioning as a hung process.

Backend initializes, but the UI hides its history

After the one-time provisioning completed, a later launch initialized the exact WSL app-server in under one second.

Codex App opened, but its task history was empty.

A direct JSON-RPC thread/list request using the same WSL CODEX_HOME, app-server flags, and request parameters returned 953 existing thread records:

  • 191 user threads
  • 618 subagent threads
  • 144 records with a null source classification

The databases and task files were present and readable. The backend returned the history, but the Desktop UI did not render it.

Persisted Windows cwd values use forms such as:

~~~text
C:\Users\<user>\...
~~~

while WSL represents the same locations as:

~~~text
/mnt/c/Users/<user>/...
~~~

This suggests a Desktop runtime/path/state classification problem rather than missing backend data.

Native rollback restores the App

Disabling Run Codex in WSL and restarting restored:

  • normal App startup;
  • the original task history; and
  • access to the existing projects.

The WSL-side state was preserved for diagnostics.

Reproduction steps

  1. Start Codex App in native Windows mode with existing local task history.
  2. Configure Ubuntu as the default WSL2 distribution.
  3. Verify /usr/bin/bash and bubblewrap are available.
  4. Enable Run Codex in WSL.
  5. Restart Codex App.
  6. Observe the first launch fail after approximately 90 seconds while the WSL app-server is still migrating and backfilling its state.
  7. Restart after the provisioning work has completed.
  8. Observe that the WSL app-server initializes, but the App displays no existing history.
  9. Query the same app-server directly with thread/list and observe that it returns the existing threads.
  10. Disable Run Codex in WSL and restart.
  11. Observe that native startup and the original history return.

Expected behavior

  • First-run WSL provisioning should report progress and should not be killed while migrations or backfill are advancing.
  • The initialization timeout should reset on progress, allow sufficient first-run time, or retry safely.
  • A failed WSL launch should offer an automatic rollback to native Windows.
  • Threads returned by thread/list should appear in the Desktop UI.
  • Windows and WSL cwd forms should be normalized consistently when grouping tasks under projects.
  • Switching environments must not make preserved task history appear lost.

Relationship to suggested duplicates

  • #32314 and #31958 cover the native elevated sandbox slowdown that motivated the WSL switch.
  • #32168 covers the unelevated split writable-root failure and is already closed as a duplicate.
  • #32182 reports one missing task after an App update. It does not involve switching to WSL, a first-run provisioning timeout, or a backend thread/list response containing the hidden history.
  • #30288 is related, but describes a short WSL eligibility timeout followed by fallback to the Windows app-server. In this reproduction, the WSL app-server launched, performed provisioning, and was terminated around the App's later initialization boundary.
  • #24364 is also related to Windows/WSL cwd migration. In this reproduction, an unfiltered thread/list request returned the complete WSL history while the Desktop UI remained empty.

Diagnostic material

Sanitized App and sandbox logs can be supplied privately through /feedback. Raw SQLite databases, task JSONL files, user paths, and conversation content are intentionally not attached publicly.

View original on GitHub ↗

2 Comments

github-actions[bot] contributor · 1 month ago

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

  • #32314
  • #31958
  • #32168
  • #32182

Powered by Codex Action

lukassarfert · 1 month ago

Adding data on this from a newer build — App 26.715.4045.0, app-server 0.145.0-alpha.18, Windows 11 Pro 26200, WSL 2.7.10.0, OracleLinux 9.5 (kernel 6.18.33.2), bubblewrap 0.6.3, roughly 393 rollout files / 547 MB of session history, 413 threads. Both failure modes @Cameloo1 reported on 26.707 / 0.144.2 are still present several builds later.

There's an exact mechanism behind the first one, and a harder variant of the second.

1. The provisioning kill has a permanent form: the backfill claim goes stale

The report describes the process being killed while it was actively working. The mechanism is visible in the app-server log:

state db backfill is running at /mnt/c/Users/<user>/.codex; waiting up to 30s before retrying startup initialization
Error: failed to initialize sqlite state runtime under /home/<user>/.codex/sqlite:
  timed out waiting for state db backfill at /mnt/c/Users/<user>/.codex after 30s (status: running)

The claim lives in backfill_state in state_5.sqlite. When a run is interrupted, that row is left at status='running' with last_success_at still NULL, and nothing expires it — there's no heartbeat or TTL. Every subsequent launch reads the row, concludes a backfill is already running elsewhere, waits its 30 seconds, and performs no work itself. The owner it waits for is long dead.

In the original report provisioning eventually completed. Here it could not, and the app became permanently unlaunchable: main, GPU, network and storage processes all start, crashpad runs, everything reports Responding=True, but no --type=renderer process is ever spawned, so no window appears. No crash dumps, nothing in the Windows event log. From a user's perspective, clicking the icon does nothing at all, indefinitely — which makes this very hard to self-diagnose.

Relaunching does not help and cannot: four consecutive launches moved last_watermark by zero bytes, because no launch does any work while the stale claim is held. Clearing the claim while preserving the watermark resolves it:

UPDATE backfill_state SET status = 'pending';

The backfill then resumed from its watermark and completed in under 40 seconds — status='complete', 413 threads, matching the Windows-side DB exactly. The next launch produced initialize_handshake_result outcome=success in 949 ms and window ready-to-show after 161 ms. Deleting the row instead also clears the lock, but discards last_watermark and forces a full rescan over /mnt/c.

For what it's worth, the 30s value appears to be hardcoded — no CODEX_* env var, config key or CLI flag in the binary touches it. #23787 mentions a 900-second backend lease against the 30-second GUI wait, which is consistent with the behaviour here.

2. Path normalization: UNC workspace roots break turn/start entirely

The path-normalization theory in the report holds, and there's a sharper case than C:\... vs /mnt/c/.... With a workspace root stored as a UNC path (\\wsl.localhost\<distro>\home\<user>\src\repos\<repo>), every message submit failed:

[Composer] submit failed  method=turn/start
cwd="/mnt/c/Program Files/WindowsApps/OpenAI.Codex_.../app/resources/\\wsl.localhost\<distro>\home\<user>\src\repos\<repo>"
error: Invalid request: AbsolutePathBuf deserialized without a base path

A UNC path isn't absolute to the Linux app-server — absolute means a leading / — so it's treated as relative and joined onto the server's own working directory. That's what "without a base path" means here: not a notation mismatch, but a relative path with nothing to resolve it against. The consequence goes beyond hidden history: with a UNC root the app is unusable, since every message fails at turn/start.

Rewriting the stored roots into Linux notation resolved it — active-workspace-roots, electron-saved-workspace-roots, thread-workspace-root-hints and local-projects in .codex-global-state.json, plus matching [projects.'/linux/path'] trust entries in config.toml. (The app rewrites config.toml from its own state on exit, so it has to be closed first.) Afterwards: turn/start with errorCode=null and no AbsolutePathBuf errors at all.

Two observations that may help scope the fix:

  • The identical error was already occurring on method=fs/watch earlier the same day in native Windows mode with that UNC root open. UNC roots trip path handling in more than one code path, not only under WSL.
  • After switching, thread not found is returned for a number of older conversation IDs while those threads are present in the DB — likely the same underlying issue as the hidden-history symptom in the report, surfacing through a different call.

Findings that look relevant but aren't

The sandbox log fills with GetSecurityInfo failed: 1 / GetNamedSecurityInfoW failed: 1 on the WSL UNC path. These are noise — 9p doesn't implement Windows ACLs, and they appear daily including on days when everything works (305 on one entirely healthy day). Likewise bundled_executable_relocation_failed for bwrap: the bundled copy really is missing from the MSIX, but the distro's /usr/bin/bwrap is present and this is not what terminates the server. A 519 MB logs_2.sqlite is a genuine concern (#27741) but was not causal here either.

Suggested fixes

  1. Add a TTL or heartbeat to the backfill claim. A running row whose owner no longer exists should expire rather than deadlock every future launch — this is what turns a slow first run into a permanently broken install.
  2. Reconcile the 30-second GUI wait with the backend lease, or surface backfill progress in the UI. Today a large history means the window never opens, with no user-visible reason.
  3. Translate stored workspace roots when switching to WSL mode, or normalize UNC paths server-side. Silently joining a \\wsl.localhost\ root onto the server's cwd is the worst of the available outcomes — rejecting it with a clear message would already be a large improvement.
  4. Include the offending path in the user-facing error. AbsolutePathBuf deserialized without a base path in a toast is unactionable; the log contained the answer immediately.