Fix Codex’s “sandboxCwd is not a local file URI” on WSL
A verified repair for Codex’s “sandboxCwd is not a local file URI” error on WSL, followed by CODEX_CLI_PATH and Windows plugin import fixes.
On this page · 11 sections
- 01The setup
- 02Failure: request validation rejected sandboxCwd
- 03What I ruled out
- 04One directory, two local file URIs
- 05The patch: map sandboxCwd only for Windows STDIO servers under WSL
- 06Version-pinned build and tests
- 07Fresh-process verification exposed two more boundaries
- 08Checks after the build
- 09What I would check first next time
- 10Status and caveat
- 11Credits
Codex Desktop ran its app server inside WSL. Chrome and Computer Use used Windows plugin runtimes. Every request failed before its JavaScript ran because Codex sent a Linux file URI to a Windows MCP server in the sandbox working-directory field, sandboxCwd.
The first fix was to translate the URI for the process that received it: /mnt/c/... to file:///C:/..., and paths inside the WSL filesystem to file://wsl.localhost/<distro>/.... I applied a public patch to the exact Codex release used by the desktop app.
A fresh process exposed two more WSL-to-Windows boundaries. CODEX_CLI_PATH had to select a Windows-native sandbox helper. Plugin clients also had to load through Windows-valid file URLs.
This note follows the error, the checks that ruled out other causes, the version-pinned build, the fresh-process repairs, and the separate Chrome recovery and browser check.
The setup
- Host application
Codex Desktop on Windows
Windows- Codex app server and shell
WSL2
WSL- Workspace
opened through WSL
WSL- Windows integrations
Chrome and Computer Use plugins
Windows- Tool transport
a Windows
Windowsnode_repl.exeregistered as a local MCP server over standard input/output (STDIO)
Codex sent each request from Linux, and the local MCP server received it on Windows. Both systems could reach the workspace, but the file URI had to use the receiving operating system's path convention.
Failure: request validation rejected sandboxCwd
The smallest test asked the MCP server to evaluate a trivial JavaScript expression and return its working directory. It failed before the expression ran:
Error
Mcp error: -32602: js: codex/sandbox-state-meta:
sandboxCwd is not a local file URI: file:///mnt/c/.../projectWhere it fails
sandboxCwd is a field in MCP tool-call _meta under codex/sandbox-state-meta. Codex sends it when the server advertises the sandbox-state capability. In this call, the Windows MCP server rejected the Linux-local URI during request validation. Browser selectors, Chrome state, and Windows input handling were downstream of that failure and had not run.
Fixes, in the order they were found
Map
sandboxCwdonly for Windows STDIO servers under WSL:/mnt/c/...becomesfile:///C:/.... Build the exact Codex release with the patch.Point
CODEX_CLI_PATHat a Windows-native Codex executable, so the Windows sandbox helper can start.Load plugin clients through Windows-valid file URLs:
file:///C:/...instead of/mnt/c/....
Not the fix
Change the MCP server's
cwd.A different field: the rejected value came from the tool-call
_meta.Remove
sandboxCwd.It bypasses the check but drops the sandbox working directory.
The public report with the same failure class was openai/codex#29639. Its workspace was stored under /home/... in the native WSL filesystem, while mine was exposed under /mnt/c/.... In both cases, a Windows .exe received a Linux-local sandboxCwd URI.
What I ruled out
I checked each boundary separately before changing the generated configuration.
The plugins were installed
- ruled out
Both Chrome and Computer Use were installed and enabled. Their tools appeared in the session.
Shows This was not a missing-plugin failure.
The local MCP server received the request
- ruled out
Codex had generated a STDIO MCP configuration entry for the Windows executable. The file existed.
Shows The structured MCP validation error showed that the process had started and received the request.
The workspace was reachable
- ruled out
WSL could read the workspace normally. Windows could address the same directory through a drive path or the WSL UNC namespace.
Shows The directory existed; the receiver rejected the URI used to describe it.
Changing the MCP server's cwd did not change sandboxCwd
- ruled out
The rejected value came from MCP tool-call
_meta, not from the process-levelcwdin the generated MCP configuration.Shows Editing that generated block changed the wrong field, and Codex Desktop could overwrite it on restart.
Removing sandbox context was not the fix
- ruled out
Removing
sandboxCwdwould bypass this validation but omit the sandbox working directory from the request.Shows The fix needed to preserve that field and encode its value for Windows.
One directory, two local file URIs
| Workspace | Path in WSL | URI local to Linux | URI for a Windows process |
|---|---|---|---|
| Mounted from a Windows drive | For a workspace mounted from a Windows drive, WSL sees:/mnt/c/work/project | The Linux-local URI is:file:///mnt/c/work/projectrejected by Windows | A Windows process needs the drive form:file:///C:/work/projectWindows form |
| Stored inside the WSL filesystem | A workspace stored inside the WSL filesystem needs a different translation. Linux may see:/home/user/project | not given in this note | Windows reaches that directory through the WSL UNC namespace:file://wsl.localhost/<distro>/home/user/projectWindows form |
Using /mnt/c was not itself the bug. The error came from sending a URI local to Linux to a process validating it under Windows rules.
Codex already knew the active WSL distribution and the MCP server's STDIO command. It did not combine those facts before attaching sandboxCwd.
The patch: map sandboxCwd only for Windows STDIO servers under WSL
I found a focused public fork commit that implemented the missing mapping: Nicolas0315/codex@1870471. I checked out the release used by Codex Desktop, applied the patch there, and kept the change scoped to this runtime boundary.
The patch:
Stores each MCP server's STDIO command in its metadata.
Detects whether Codex is running under WSL.
Maps
sandboxCwdonly for a Windows.exeSTDIO command launched through WSL interop.Maps
/mnt/<drive>/...to a Windows drive file URI.Maps other WSL paths to a
wsl.localhostUNC file URI.Leaves Linux commands, non-WSL hosts, Streamable HTTP MCP servers, and already compatible paths unchanged.
In reduced form:
if is_wsl() && windows_exe_via_wsl_interop(stdio_command) {
sandbox_cwd = map_wsl_uri_for_windows(sandbox_cwd, distro_name);
}
The server-type check prevents the mapping from changing requests sent to Linux MCP servers.
Version-pinned build and tests
I built from the release tag used by the desktop app with Cargo's lockfile unchanged. The adjacent Codex executables remained on that same release. Before the release build, I ran the test groups touched by the patch:
- 6 passed
WSL
sandboxCwdmapping tests - 1 passed
MCP server metadata regression test
- passed
Formatting and targeted fix checks
- passed
Locked release build
The mapping tests covered both workspace locations:
/mnt/c/...becomesfile:///C:/..../home/...becomesfile://wsl.localhost/<distro>/home/....
The guardrail tests also kept the original URI for a Linux STDIO command and for a non-WSL host.
Fresh-process verification exposed two more boundaries
The running desktop process could reach Computer Use. That did not prove that a new CLI process could reconstruct the same chain from its saved configuration. Fresh-process testing separated four runtime roles that had looked like one configuration problem:
| Runtime role | Uses | Platform |
|---|---|---|
| WSL Codex caller | patched Linux Codex executable | WSL |
| Windows MCP host | Windows node_repl.exe | Windows |
| Windows sandbox helper | Windows Codex executable via CODEX_CLI_PATH | Windows |
| plugin module specifier | Windows-valid file URL | Windows |
Plain codex still selected the stock launcher
The desktop app had an explicit path to the patched Linux executable. A plain codex command in WSL did not use that setting; shell resolution still selected the stock package launcher. I added a higher-priority CLI entry that points to the verified patched build and left the package-managed launchers installed as rollback paths.
This fixed launcher selection without replacing the package installation.
The Windows sandbox helper tried to execute a Linux binary
The Windows node_repl.exe creates its sandboxed Node child through a Codex helper selected by CODEX_CLI_PATH. Pointing that variable to the Linux executable produced Windows error 193: the file was a valid Linux ELF, but not a valid Win32 application.
The platform-specific executable roles therefore had to diverge:
| Role | Executable | Platform |
|---|---|---|
| The WSL-hosted Codex caller | uses the patched Linux executable | WSL |
| The Windows MCP host | uses the bundled Windows node_repl.exe | Windows |
| The helper selected by the Windows MCP environment | uses a Windows-native Codex executable | Windows |
I registered the command name codex-wsl-bridge on both platforms. WSL resolves it to the patched Linux build; Windows resolves it to the bundled PE. The generated configuration can therefore use one command name while each host runs the correct executable.
I kept the sandbox enabled. Disabling it also avoided the failing child process, but would have removed the isolation instead of fixing the executable boundary.
Dynamic imports needed Windows-valid module specifiers
The integration is installed as a plugin, but Windows Node loads the plugin's .mjs client with import(). Once Node started reliably, a module path discovered by the Linux process could still arrive as:
await import("/mnt/c/.../computer-use-client.mjs")
Windows Node needs a Windows-valid file URL:
await import("file:///C:/.../computer-use-client.mjs")
The installed plugin instructions convert this path during normal use. I also added a narrowly matched, trusted PreToolUse path-rewrite hook for deterministic enforcement. The hook changes only /mnt/<drive>/... string literals inside dynamic imports sent to the Node REPL. It leaves shell paths, Linux-native tools, and unrelated JavaScript unchanged. Codex records trust against the exact hook definition, so a later edit disables it until the new definition is reviewed.
Checks after the build
The six WSL-to-Windows checks passed. Chrome required a separate recovery sequence and end-to-end browser check.
1. Direct MCP call
- passed
The original JavaScript probe ran and returned a Windows working directory.
Shows The mapped
sandboxCwdhad passed request validation.
2. Plain CLI process from a Windows-mounted workspace
- passed
A new non-interactive
codexprocess started from/mnt/c/..., reached the Windows Node runtime, and completed a Computer Use bootstrap without receiving a corrected path manually.
3. Plain CLI process from the native WSL filesystem
- passed
I repeated the fresh-process test from a native WSL directory. It reached the same sandboxed Windows runtime and returned the same Computer Use inventory.
Shows This checked both workspace URI mappings rather than proving only the
/mnt/ccase.
4. Deliberately raw plugin import
- passed
I disabled the persistent mapping instruction for this probe, passed an exact raw
/mnt/c/...dynamic import, and did not bypass hook trust. The trusted PreToolUse path-rewrite hook changed the call tofile:///C:/...before Windows Node received it. The same import completed when the WSL-hosted CLI started from either a Windows-mounted workspace or the native WSL filesystem.
5. Computer Use application discovery
- passed
A read-only Computer Use discovery call returned 40 Windows applications. No app was launched or manipulated.
6. Post-restart acceptance check
- passed
The desktop app had been holding the previous Node transport in memory. After a full restart, the reopened app had a newer process start time and loaded the plugin client through
file:///C:/.... A direct JavaScript probe returned a Windows working directory, and read-only Computer Use discovery again returned 40 applications. No app was launched or manipulated.
7. Chrome connection recovery and end-to-end check
- not available
Chrome as an available browser
Chrome still did not appear as an available browser after the
sandboxCwdfix.
These checks passed:
- passed
Chrome was installed and running.
- passed
The ChatGPT Chrome Extension was installed and enabled in the selected profile.
- passed
The native-messaging manifest was present and valid.
Recovery sequence, not a root-cause fix
- availability returned
Chrome availability returned after I reinstalled the plugin, reopened Codex, opened one blank Chrome window in the selected profile, and retried the connection.
Does not show I did not identify which component had gone stale, so this remains a recovery sequence rather than a second root-cause fix.
- passed
End-to-end check
A later end-to-end check used Codex to open a tab in the selected Chrome profile. A separate local Manifest V3 prototype then applied a reviewed native tab-group plan without closing tabs or reading page content. The prototype was an acceptance exercise, not part of the
sandboxCwdfix or a second root-cause diagnosis.
These were separate failures. The URI mapping restored MCP request transport and Computer Use. The Chrome sequence restored the browser connection, and the tab-opening check verified it with a real browser action.
What I would check first next time
I would use this order of diagnosis again:
Run the smallest MCP call. If request validation fails before user code, UI selectors, tabs, and clicks are downstream of the failure.
Name both processes. “Codex on Windows” hides the useful detail: a Codex app server in WSL was calling a Windows
.exeover STDIO.Inspect tool-call metadata and process configuration separately. A valid executable and process
cwddo not make a foreignsandboxCwdURI valid.Preserve the sandbox context. Translate its working-directory URI for the receiver instead of removing it.
Build the exact release. Desktop integrations can depend on matching executables and protocol versions.
Check each layer. Test the URI mapping, platform-specific executable resolution, the Windows sandbox helper, a raw plugin import, Computer Use discovery, and the restarted desktop app.
Keep separate failures separate. A fixed MCP request path can expose a later plugin-session problem. After recovery, verify one real browser action instead of stopping at availability.
Status and caveat
As of July 10, 2026, openai/codex#29639 remains open, has no linked pull request, and upstream main does not contain this mapping. The commit is a public fork patch rather than an official release.
I kept a rollback copy, pinned the build to the installed Codex version, and would replace it with an upstream release once the same mapping ships there.
For related Windows and WSL failures, see my Codex issue reproductions and debugging notes.
Credits
- atlantho
- published the anonymized reproduction in openai/codex#29639, including the native WSL
/home/...case. - Nicolas0315
- wrote the focused WSL-to-Windows
sandboxCwdmapping patch used for the compatibility build. - OpenAI Codex
- its open-source request path made it possible to inspect, test, and build the matching release.
- Codex agent
- helped trace the request metadata and repeat the Computer Use checks after the patched build was installed.