Windows Desktop + WSL app-server reads WSL ~/.codex instead of the Windows Codex home
What version of Codex is running?
- Codex Desktop app package:
OpenAI.Codex_26.506.3741.0_x64 codex-cli 0.130.0-alpha.5
What subscription do you have?
Pro
Which model were you using?
GPT-5.5
What platform is your computer?
Windows 11 Desktop app with WSL2 Ubuntu enabled. The Codex app is the Windows desktop app, but the app-server is launched through WSL.
What issue are you seeing?
In this hybrid Windows Desktop + WSL setup, Codex appears to read different config homes depending on launch context:
- The Windows-side CLI / desktop settings UI use the Windows Codex home.
- The WSL app-server launched by the Windows desktop app reads the WSL user's
~/.codex/config.toml.
That creates split-brain config behavior. A feature flag or setting changed in the Windows config can silently differ from what the running app-server actually uses.
This caused user-visible issues here:
- Deprecation warnings in the desktop UI were coming from deprecated keys in the WSL config, even though the Windows config no longer had those keys.
- The shell-runner regression tied to
unified_execreappeared because the WSL config still hadunified_exec = truewhile the Windows config had already been set tofalse.
Steps to reproduce
- Use Codex Desktop on Windows in a WSL-backed workflow.
- Make the Windows config and WSL config intentionally differ:
Windows config:
[features]
unified_exec = false
multi_agent = true
hooks = true
WSL config:
[features]
unified_exec = true
collab = true
- Launch Codex Desktop and open a WSL-backed workspace.
- Observe desktop behavior such as:
- config deprecation warnings
- effective shell-runner behavior
- feature state as seen by the WSL app-server
Expected behavior
The Windows desktop app and the app-server it launches should use one coherent config source, or there should be an explicit documented precedence model that is visible to the user.
At minimum, if the app-server is going to use WSL ~/.codex, the desktop app should surface that clearly instead of making the Windows config look authoritative.
Actual behavior
The running WSL app-server used the WSL home config, while the user-facing Windows app configuration lived in the Windows Codex home.
Observed evidence:
CODEX_HOME=/mnt/c/Users/.../.codexwas present in the interactive shell environment.- The running Windows Codex process launched the WSL app-server with a command line equivalent to:
wsl.exe -d Ubuntu --cd ".../resources" -- /usr/bin/bash -lc "... exec /mnt/c/Users/.../.codex/bin/wsl/codex app-server --analytics-default-enabled"
- That launch path did not visibly pass
CODEX_HOMEthrough to the WSL app-server process. - After inspecting
/home/<user>/.codex/config.toml, it contained the deprecatedcollab = truekey that matched the warnings shown in the Windows app. - Changing
/home/<user>/.codex/config.tomlimmediately changed the effective feature set of the WSL app-server.
Why this is a bug
In a Windows desktop product, users reasonably expect the desktop app's config home to be the config the app actually uses.
Using the WSL user's ~/.codex/config.toml behind the scenes creates:
- confusing deprecation warnings
- feature flag drift
- inconsistent shell-runner behavior
- difficult recovery, because changing the visible Windows config may not fix the live app
Related issue
This seems related to, but distinct from, the shell-runner bug in #22185.
That issue describes the unified_exec failure mode itself. This issue is about the Windows desktop app and its WSL app-server using different config homes, which can unexpectedly reactivate that failure even after the Windows config is fixed.
Suggested fix
One of:
- Ensure the Windows desktop app passes the intended
CODEX_HOMEinto the WSL app-server process. - Standardize on the Windows config home for Windows Desktop sessions, even when the app-server runs inside WSL.
- If dual-home behavior is intentional, expose the active config path in the UI and logs so users can tell which config file is authoritative.
4 Comments
Additional data point from the same Windows Desktop + WSL setup after auditing both config homes.
Current state:
/mnt/c/Users/<user>/.codex/config.toml/home/<wsl-user>/.codex/config.toml[features] unified_exec = false, which prevents the shell regression from being reintroduced by config drift.The confusing part is that there are two competing requirements:
C:\Users\<user>\.codex./mnt/c/Users/<user>/.codex.I tested a guarded WSL shell-profile override that sets
CODEX_HOME=/home/<wsl-user>/.codex-apponly whenCODEX_INTERNAL_ORIGINATOR_OVERRIDE=Codex Desktop. It is a real workaround for the/mnt/cperformance/worktree issue, but I reverted it because it makes config/home split-brain worse:config.tomlis effective without inspecting runtime env.Suggested fix direction: split these concepts explicitly instead of overloading
CODEX_HOME:That would avoid both the Windows-side
/mnt/cperformance issue and the current hidden config precedence ambiguity.I want to avoid fragmentation here, so I’m treating this as a follow-up to #22759 and linking the closely related reports that appear to cover the same failure modes:
What to check in compare
Please check the working diff here:
https://github.com/openai/codex/compare/main...pussikill:codex/wsl-desktop-runtime-cache
The changes are scoped to:
1) separating persistent windows auth/state home from WSL runtime cache root,
2) moving plugin/skills runtime-heavy caches to
~/.codex/runtime/wsl/<distro>while keeping auth/session/config in Windows-backed home,3) making arg0 temp cleanup safe for live runs,
4) skipping unsupported ACL refresh for
\\wsl.localhost-style UNC paths,5) adding an explicit warning + opt-in flag (
CODEX_DESKTOP_WSL_NATIVE_CODEX_HOME) for users who explicitly want native WSL.codex.Repro and measurement steps
cargo build -p codex-app-serverfromcodex/codex-rs.launch-codex-desktop-local-wsl.sh --stop-existing) where:CODEX_CLI_PATHis set to UNC path\\wsl.localhost\<distro>\home\...\codex-rs\target\debug\codexCODEX_APP_SERVER_FORCE_CLI=1CODEX_HOME=/mnt/c/Users/.../.codex,CODEX_INTERNAL_ORIGINATOR_OVERRIDE=Codex Desktop,WSL_DISTRO_NAME=<distro>.python3 bench-wsl-desktop-app-server.py --isolated --clean --iterations 10bench-results/*JSON/CSV in the PR/check run.Current numbers
414ms -> 286ms(cold),427ms -> 233ms(warm)691ms -> 99ms(cold),712ms -> 95ms(warm)715ms -> 101ms(cold),718ms -> 95ms(warm)I’d request triage focus on #22759 + these related reports together so we avoid duplicate fixes across home selection, ACL paths, and plugin-cache latency.
I ran a second benchmark in a hot-path scenario (same codepath, no clean reset,
--isolatedonly) to validate the production-like working behavior after warm cache is in place.Run:
python3 bench-wsl-desktop-app-server.py --iterations 8 --isolatedOutput dir:
bench-results/20260608-134520Hot-path median (8 runs):
initialize: store400.639ms-> patched216.166msskills/list: store630.179ms-> patched95.272msskills/list:forceReload: store668.978ms-> patched95.043msplugin/list: store9.463ms-> patched8.280ms--isolatedenv remained separate and stable, and the patched path kept runtime data in~/.codex/runtime/wsl/Ubuntuwhile leaving Windows HOME clean.So this is not only a cold-start gain, but also a sustained improvement in real/reused-session flow.
I checked the non-isolated run more carefully to avoid comparing unrelated state.
The whole Windows
CODEX_HOMEsize is not the right primary metric because it also contains persistent auth/session/config/log state. The comparable footprint is the plugin temp runtime tree used by plugin/skill discovery.Comparable plugin temp tree:
/mnt/c/Users/xdev/.codex/.tmp/plugins/home/xdev/.codex/runtime/wsl/Ubuntu/.tmp/pluginsThe remaining differences are mostly git metadata/refs, so this is effectively the same runtime plugin tree, just served from different filesystems.
The RPC result size was also the same:
config/read: 3 layersplugin/list: 2 marketplacesskills/list: 1 skillNon-isolated single-run timing on the real local environment:
initialize: store547.8ms-> patched219.9msplugin/list: store1125.0ms-> patched479.4msskills/list: store28816.7ms-> patched374.0msskills/list:forceReload: store19196.9ms-> patched602.2msSo the important datapoint is not “different returned data” or “different cache size”. The returned data is the same and the comparable plugin temp tree is nearly identical. The difference is that the store bundle walks that runtime tree through
/mnt/c, while the patched build keeps persistent Desktop state on Windows but serves heavy runtime plugin/skill cache from native WSL storage.This is a single-run non-isolated sanity check, not the primary statistical benchmark. I would use it mainly as confirmation that the same bottleneck reproduces in the real user environment, while the isolated multi-run benchmarks remain the cleaner apples-to-apples timing data.