Regression: Windows Desktop + WSL shell stays poisoned after reboot; live app-server holds deleted CODEX_HOME/tmp/arg0 helper path despite unified_exec=false

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

Summary

This is a regression / worse state than the already-reported Windows Desktop + WSL shell failures in #22185 and #16970.

  • #22185 covered unified_exec=true causing Windows Desktop + WSL to try to CreateProcess a Linux shell path such as /bin/bash. The workaround was unified_exec=false.
  • #16970 covered stale CODEX_HOME/tmp/arg0/codex-arg0* helper paths where restarting Desktop generally regenerated a working helper directory.
  • This report is worse: unified_exec=false is already set in both the Windows Desktop Codex home and the WSL Codex home, the machine was fully rebooted more than once, and a fresh Desktop + WSL app-server session can still reach a poisoned state where the active shell/tool runtime is pinned to a deleted tmp/arg0 helper directory.

In other words, reboot/relaunch is no longer a reliable recovery for this failure class.

Environment

  • Windows Desktop package: OpenAI.Codex_26.527.3686.0_x64__2p2nqsd0c76g0
  • Desktop release string in logs: 26.527.31326
  • Electron runtime: 42.1.0
  • Active WSL app-server / hashed WSL agent: codex-cli 0.135.0-alpha.1
  • Stable WSL agent path still present: codex-cli 0.130.0-alpha.5
  • Platform: Windows 11 + WSL2 Ubuntu
  • WSL kernel observed: 6.6.114.1-microsoft-standard-WSL2
  • Desktop CODEX_HOME: %USERPROFILE%\.codex, seen from WSL as /mnt/c/Users/<user>/.codex
  • Shell in the affected Desktop session: /bin/bash
  • unified_exec=false in both:
  • %USERPROFILE%\.codex\config.toml
  • <wsl-home>/.codex/config.toml

Actual Behavior

Normal shell/tool calls fail before the requested command starts:

execution error: Io(Os { code: 2, kind: NotFound, message: "No such file or directory" })

The workspace path is not the problem. The active session's PATH contains a stale helper directory:

/mnt/c/Users/<user>/.codex/tmp/arg0/codex-arg0UXHjoV

That directory no longer exists:

stat: cannot statx '/mnt/c/Users/<user>/.codex/tmp/arg0/codex-arg0UXHjoV': No such file or directory

However, the live WSL app-server still holds an fd to the stale helper lock path:

/proc/<app-server-pid>/fd/3 -> /mnt/c/Users/<user>/.codex/tmp/arg0/codex-arg0UXHjoV/.lock

And fdinfo shows the live process still has a POSIX advisory write lock on that old lock inode:

lock: 1: POSIX  ADVISORY  WRITE <app-server-pid> ...

Later, a new codex-arg0* directory can appear under the same tmp/arg0 root with valid Linux symlinks:

apply_patch -> /mnt/c/Users/<user>/.codex/bin/wsl/<hash>/codex
applypatch -> /mnt/c/Users/<user>/.codex/bin/wsl/<hash>/codex
codex-execve-wrapper -> /mnt/c/Users/<user>/.codex/bin/wsl/<hash>/codex
codex-linux-sandbox -> /mnt/c/Users/<user>/.codex/bin/wsl/<hash>/codex

But this does not repair the active thread, because the active thread/app-server state remains pinned to the deleted codex-arg0UXHjoV helper path.

Why This Is Not Just a Duplicate

This is related to #16970 and #22185, but it is not the same recovery state:

  • unified_exec=false is already applied in every relevant config.
  • A full OS reboot was attempted more than once.
  • The failure still reappears in the current Desktop build family.
  • The live app-server can retain an fd/lock for a helper path that is already gone from the filesystem.
  • Creating a newer helper directory does not rebind the active shell/tool runtime.

So the old practical workaround, "restart/reboot Desktop so a fresh helper dir is created," is no longer sufficient.

Source-Level Findings

I checked current origin/main of openai/codex at:

8acaec73b6a227ae9375064e910ecf86f2620ec0

Relevant current behavior in codex-rs/arg0/src/lib.rs:

  • The helper root is shared under find_codex_home()/tmp/arg0.
  • Unix creates helper symlinks in that root: apply_patch, applypatch, codex-execve-wrapper, codex-linux-sandbox.
  • Windows creates .bat helper shims in the same root.
  • Cleanup is lock-only: janitor_cleanup() attempts to lock each child dir and deletes it if locking succeeds.

This is fragile in Windows Desktop + WSL because the Desktop home is shared across a Windows process family and a WSL/Linux process family, and in this setup CODEX_HOME is on DrvFS under /mnt/c.

PR #17570 adds a .pid owner sentinel and appears to address the Linux-side stale-helper case, but I do not see that commit on origin/main. Also, as currently written on that branch, the owner PID liveness check returns false on non-Linux. That means it may not fully protect a WSL-owned helper directory if a Windows-native Codex process is janitoring the same shared %USERPROFILE%\.codex\tmp\arg0 tree.

Related Runtime Evidence

The same Desktop logs show mixed runtime surfaces in the same launch:

stdio_transport_spawned ... executablePath=%USERPROFILE%\.codex\bin\wsl\<hash>\codex ... spawnCommand=wsl.exe
Current reported app-server version: currentVersion=0.135.0-alpha.1

But Browser/Computer Use runtime selection in the same session selected Windows-native paths:

browser_use_runtime_paths_selected codexCliPath=%LOCALAPPDATA%\OpenAI\Codex\bin\<hash>\codex.exe ... platform=win32
computer-use native pipe startup failed ... "Windows Computer Use helper paths are unavailable"

I am not claiming the Browser/Computer Use plugin failure is the root cause of the shell failure. I am including it because it shows the same Desktop session crossing Windows-native and WSL-native runtime/cache surfaces while sharing the same Codex home.

Expected Behavior

For Windows Desktop + WSL sessions:

  • unified_exec=false should remain a reliable mitigation for the Windows CreateProcess(/bin/bash) failure class.
  • Reboot/relaunch should not deterministically return to a poisoned helper state.
  • A live app-server should not have its active tmp/arg0 helper directory deleted while it still uses paths inside it.
  • If a cached codex-linux-sandbox / arg0 helper path returns ENOENT, the runtime should rehydrate/rebind helper paths instead of leaving the thread permanently broken.

Suggested Fix Direction

One or more of:

  1. Namespace arg0 helper dirs by runtime family/platform/distro, for example separate Windows-native helpers from WSL/Linux helpers instead of sharing CODEX_HOME/tmp/arg0.
  2. Add a helper manifest containing platform, helper kind, owner PID, and WSL distro/runtime identity; janitors should only clean dirs from their own runtime family.
  3. Make the active-helper ownership check cross-runtime safe. If a Windows process cannot verify a WSL PID, it should not treat that helper dir as safe to delete.
  4. On ENOENT for cached codex_linux_sandbox_exe / arg0 helper paths, rebind once from the live app-server's current helper guard or recreate a helper dir, then retry.
  5. Add a Windows Desktop + WSL release-gate test with:
  • CODEX_HOME on %USERPROFILE%\.codex / /mnt/c/Users/<user>/.codex
  • unified_exec=false
  • WSL app-server active
  • repeated Desktop restarts/reboots or workspace/thread switches
  • verification that shell calls still run and PATH does not point at a deleted codex-arg0* dir.

Related Issues / PRs

  • #22185: earlier unified_exec=true / Windows CreateProcess(/bin/bash) failure
  • #16970: stale tmp/arg0 helper dir ENOENT until restart
  • #17570: proposed .pid sentinel fix for active arg0 helper dirs
  • #22759: Windows Desktop + WSL config/Codex-home split
  • #25216: umbrella release-gate issue for Windows Desktop + WSL

View original on GitHub ↗

7 Comments

github-actions[bot] contributor · 1 month ago

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

  • #25280

Powered by Codex Action

MisterRound · 1 month ago

I reviewed the duplicate bot suggestion, #25280. It is related and probably the closest existing report, but I do not think this issue should be closed as a straight duplicate.

The difference I am trying to isolate here is the regression state and live ownership evidence:

  • This repro is specifically after the older mitigation is already in place: unified_exec=false in both Windows and WSL configs.
  • Full reboot/relaunch was already attempted multiple times, so this is worse than the original "restart regenerates a good helper dir" state from #16970.
  • The live WSL app-server still has an fd to the deleted helper dir's .lock path, and fdinfo shows a POSIX advisory write lock owned by that live app-server pid.
  • Later/newer codex-arg0* helper dirs can exist, but the active thread remains pinned to the deleted helper path.

#25280 is broader: it also tracks elevated Windows sandbox setup/refresh failures and Windows-local repo sandbox behavior. This issue is intended to be the narrower WSL/Desktop arg0 lifecycle regression with the fd/lock evidence and the "reboot no longer recovers" behavior.

MisterRound · 1 month ago

Additional live workaround/root-cause isolation from the same affected session.

I tested a non-destructive live rehydrate of the exact stale helper path referenced by PATH:

/mnt/c/Users/<user>/.codex/tmp/arg0/codex-arg0UXHjoV

Steps used, sanitized:

  1. Recreate the exact missing codex-arg0* directory.
  2. Recreate .lock.
  3. Add Linux symlinks:
apply_patch -> /mnt/c/Users/<user>/.codex/bin/wsl/<hash>/codex
applypatch -> /mnt/c/Users/<user>/.codex/bin/wsl/<hash>/codex
codex-execve-wrapper -> /mnt/c/Users/<user>/.codex/bin/wsl/<hash>/codex
codex-linux-sandbox -> /mnt/c/Users/<user>/.codex/bin/wsl/<hash>/codex

Result: normal non-escalated shell calls recovered in the same active thread. pwd, uname -a, and command -v codex-linux-sandbox succeeded. apply_patch also recovered and successfully edited a probe file.

Two important extra findings:

  1. WSL/DrvFS can get into an inconsistent recreate state for the deleted helper path. stat and Windows PowerShell Get-Item report the helper path missing, but WSL mkdir() returns EEXIST. Creating the directory from Windows PowerShell with New-Item -ItemType Directory works around that state; then WSL can add symlinks once the path becomes visible.
  1. Running the Windows-native bundled codex.exe after the rehydrate deletes the repaired WSL helper directory and breaks the active WSL shell again. A new Windows .bat helper dir appears under the same shared CODEX_HOME/tmp/arg0 root. Holding a WSL flock on the recreated .lock did not protect the directory from the Windows-native cleanup path.

That makes the mixed-runtime root cause much more concrete: a Windows-native Codex process and a WSL/Linux Codex app-server share the same %USERPROFILE%/.codex/tmp/arg0 namespace. The Windows-native arg0 janitor can delete the WSL helper dir that the live WSL app-server/thread still has cached, and WSL advisory locking does not reliably protect it.

This also changes the workaround guidance:

  • Rehydrating the exact stale helper dir can recover the current thread temporarily.
  • It is not durable if Windows-native Codex/plugin/browser-use paths run afterward.
  • The durable product fix needs platform/runtime namespacing or a janitor rule that never deletes helper dirs from another runtime family.
MisterRound · 1 month ago

One more workaround test result: holding the repaired helper dir open from WSL does not prevent the Windows-native cleanup.

I tried two protection variants after rehydrating the exact stale WSL helper dir:

  1. Start a long-running WSL process holding an exclusive flock on the repaired .lock file.
  2. Start a long-running WSL process with its current working directory inside the repaired codex-arg0* helper dir.

Then I invoked the Windows-native bundled codex.exe again.

Result: in both cases, the repaired WSL helper dir was later deleted and a new Windows .bat helper dir appeared under the same shared CODEX_HOME/tmp/arg0 root. Normal WSL shell/tool calls broke again.

So this is not just "WSL did not hold the right lock." Handle-based local protection from WSL is not a reliable workaround against the Windows-native arg0 cleanup path. The practical local workaround is only to rehydrate after the break, and then avoid triggering Windows-native Codex/plugin/browser-use paths. The product fix needs to stop Windows and WSL runtimes from janitoring each other's helper dirs in the first place.

poroburu · 1 month ago

I supplied a workaround script in #25799 based upon your diagnosis. It is not durable and needs to be run every Codex session.

kosei-w90607 · 1 month ago

Adding another sanitized reproduction that appears to match this Desktop + WSL failure family.

Setup:

  • Windows 11 + WSL2
  • Codex App settings:
  • Default open location: WSL
  • Agent environment: Windows Subsystem for Linux
  • Integrated terminal shell: WSL
  • WSL distro: Ubuntu-22.04
  • Workspace: WSL-hosted git repository under /home/<user>/Projects/<repo>
  • WSL login shell: /home/linuxbrew/.linuxbrew/bin/zsh (Homebrew zsh)
  • Direct WSL terminal can resolve the shell and bash:
  • echo "$SHELL" -> /home/linuxbrew/.linuxbrew/bin/zsh
  • getent passwd "$USER" ends with /home/linuxbrew/.linuxbrew/bin/zsh
  • command -v zsh -> /home/linuxbrew/.linuxbrew/bin/zsh
  • /home/linuxbrew/.linuxbrew/bin/zsh is a symlink to ../Cellar/zsh/5.9/bin/zsh
  • command -v bash -> /usr/bin/bash
  • Relevant config currently observed:
  • approval_policy = "untrusted"
  • sandbox_mode = "workspace-write"

Symptoms from Codex App:

  • exec_command stops before the requested command reaches WSL.
  • The default shell /home/linuxbrew/.linuxbrew/bin/zsh is reported as not found.
  • /bin/bash and /usr/bin/bash are reported as No such file or directory.
  • PTY execution fails because codex-linux-sandbox is not present at a temporary helper path.
  • Node-side attempt to spawn wsl.exe returns EPERM.
  • The app terminal snapshot still shows a WSL cwd and git branch, but requested command output such as pwd, echo "$SHELL", or git status --short --branch was not obtained because execution fails before the command starts.

This makes it look less like a repository or WSL shell configuration issue and more like the Desktop WSL command runner / sandbox helper failing before entering WSL. The direct WSL shell is healthy, including Homebrew zsh and /usr/bin/bash.

Update after further testing:

  • wsl.exe -d Ubuntu-22.04 --cd /home/<user>/Projects/<repo> --exec pwd succeeds and returns the expected WSL repo path.
  • wsl.exe ... --exec sh -lc 'echo "$SHELL"' succeeds and returns /home/linuxbrew/.linuxbrew/bin/zsh.
  • wsl.exe ... --exec git status --short --branch succeeds and shows the expected branch/worktree state.
  • Normal Codex Desktop shell execution still fails before reaching WSL because it appears to try launching the WSL shell path from the Windows side, effectively a Windows-native CreateProcess("/home/linuxbrew/.linuxbrew/bin/zsh", ...) style failure.

So the WSL distro, repo, git, and Linuxbrew zsh are healthy when invoked through explicit wsl.exe --exec. The failing path appears to be the Desktop normal exec path selecting or falling back to a Windows-native process launch for a Linux shell path instead of wrapping the shell command through WSL.

One caveat: a Git status signal from Desktop should not necessarily be treated as proof that normal shell execution is healthy. Git/repository status may be coming from a Desktop/plugin/MCP/internal repository metadata path rather than the same exec_command path that tries to launch the shell.

MisterRound · 1 month ago

Follow-up with the latest sanitized local findings from the same Windows Desktop + WSL investigation.

The original diagnosis still holds, but the current reproduction is sharper: this is a mixed-runtime arg0 lifecycle problem, not a missing repo/shell/bwrap problem.

Current locally proven behavior:

  • A WSL-backed Desktop app-server can be pinned to a helper path like:
%USERPROFILE%\.codex\tmp\arg0\codex-arg0<hash>
  • When that exact helper dir disappears, shell/tool calls fail before the requested command starts:
Io(Os { code: 2, kind: NotFound, message: "No such file or directory" })
  • Recreating the exact stale helper dir with Linux symlinks restores the live thread temporarily:
apply_patch -> %USERPROFILE%\.codex\bin\wsl\<hash>\codex
applypatch -> %USERPROFILE%\.codex\bin\wsl\<hash>\codex
codex-execve-wrapper -> %USERPROFILE%\.codex\bin\wsl\<hash>\codex
codex-linux-sandbox -> %USERPROFILE%\.codex\bin\wsl\<hash>\codex
  • The local recovery script now preserves an existing .lock, can recover from WSL/DrvFS oddities where WSL mkdir() reports EEXIST while stat/Windows APIs report the path missing, and can fall back to the live app-server lock fd when visible.

The additional finding is that Computer Use/browser-use sidecars can make this worse:

  • Product-generated Windows-native node_repl.exe -> codex.exe app-server --listen stdio:// sidecars can run with shared CODEX_HOME=%USERPROFILE%\.codex.
  • Those Windows-native sidecars create .bat arg0 helpers under the same shared %USERPROFILE%\.codex\tmp\arg0 root.
  • When that happens in a WSL-backed Desktop session, shared-root Windows .bat helpers and WSL/Linux symlink helpers are in the same janitor namespace.
  • Cleanup/recovery that only creates a new helper dir is not durable if the active app-server remains pinned to the deleted helper path or if another runtime family can janitor the shared root again.

Current source/PR reading still suggests the durable fix should be one or both of:

  1. Runtime-family helper namespacing, e.g. tmp/arg0/windows/... and tmp/arg0/wsl/<distro>/..., with janitors sweeping only their own family.
  2. A helper manifest with runtime family, owner PID namespace, creator executable, helper kind (bat vs symlink), and a safe cross-runtime cleanup rule.
  3. A one-shot rehydrate/rebind-on-ENOENT path for cached helper executable paths before surfacing shell failure.

Local acceptance gate until upstream fixes this:

  • shared %USERPROFILE%\.codex\tmp\arg0 contains only WSL/Linux helper dirs during WSL app-server sessions;
  • Windows .bat helpers are allowed only in an isolated Windows runtime home, not the shared Desktop home;
  • no bare/shared-home node_repl.exe -> codex.exe app-server --listen stdio:// sidecar chains are live;
  • if pre-exec ENOENT happens, rehydrate the exact stale helper dir before trying /bin/bash -lc, repo-path checks, or bwrap debugging.

This is also why #26985 looks directionally right: moving runtime-heavy WSL temp/cache state out of the shared Windows-backed home and/or namespacing arg0 by runtime family directly addresses the cross-runtime janitor collision.