Windows sandbox: setup payload passed on the command line exceeds the CreateProcess limit (os error 206)
Environment
- Windows 11
- Codex Desktop app 26.810.7004.0 (helper: app\\resources\\codex-windows-sandbox-setup.exe); sandbox bin: codex-command-runner-0.148.0-alpha.9.exe, codex.exe
- sandbox_mode = "workspace-write", [windows] sandbox = "elevated", LongPathsEnabled = 1
Symptom
Every command executed through the Windows sandbox fails before it even runs:
[codex.exe] setup refresh: spawning ...\\codex-windows-sandbox-setup.exe (cwd=..., payload_len=35044)
[codex.exe] setup refresh: setup refresh failed to launch helper: helper=...\\codex-windows-sandbox-setup.exe, ...
error=The filename or extension is too long. (os error 206)
This is ERROR_FILENAME_EXCED_RANGE returned by CreateProcess: the assembled command line
(helper path + cwd + embedded payload) exceeds Windows' 32,767-character limit.
Repro
- Place ~700 loose files directly in the user profile (e.g. C:\Users\<user>\*.sh).
- Run any command through the Windows sandbox (default, non-escalated path).
- The sandbox "setup refresh" step spawns codex-windows-sandbox-setup.exe with a payload
whose size grows roughly linearly with the top-level item count of the user profile.
Observed in %USERPROFILE%\.codex\.sandbox\sandbox.YYYY-MM-DD.log:
- ~150 top-level items -> payload_len=15108, helper completes normally
- ~500 top-level items -> still fine
- ~700 top-level items -> payload_len=35044, CreateProcess fails with 206;
EVERY sandboxed command fails from then on.
- Escalated (non-sandboxed) commands bypass the helper and keep working,
which makes the failure mode hard to diagnose.
Boundary (useful data point)
The threshold lies between 500 and 700 top-level profile items. After moving ~200 items
into a subfolder (697 -> 498 top-level entries) the sandbox recovered immediately on the
next command, no restart required.
Suggested fixes
- Pass the setup payload to the helper via a temp file or a pipe instead of the command line.
- Or keep the embedded profile-item list bounded (only roots that actually exist, or a
digest instead of the full flat list).
Logs
%USERPROFILE%\.codex\.sandbox\sandbox.YYYY-MM-DD.log
2 Comments
Verified against
main@ 1f41cc5d92 — your diagnosis is exactly right, and the code shows both why the payload is unbounded and why it ended up on the command line in the first place.The two halves of the mechanism:
collect_existing_glob_matchesrecursively walks the scan roots and pushes each concrete match:https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/windows-sandbox-rs/src/deny_read_resolver.rs#L84-L136
With deny globs anchored at the user profile, the list scales with your top-level item count — your ~50-bytes-per-item slope (15,108 chars at ~150 items → 35,044 at ~700) is just path length × base64 overhead.
SetupPayloadto JSON, base64-encodes it, and passes it as a single argument to the helper:https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/windows-sandbox-rs/src/setup.rs#L352-L377
Base64 inflates by 4/3, so the effective JSON budget under the 32,767-char
CreateProcesslimit is only ~24 KB — a ceiling any large profile can hit, as you demonstrated.Why argv was chosen (and why the fix is still easy): the initial setup path launches the helper via
ShellExecuteExWfor UAC elevation (setup.rs#L916+), and ShellExecute can only pass parameters through the command line — no stdin piping. That constraint got inherited by the refresh path, but the refresh explicitly never elevates (the comment at #L373 says so) and spawns via plainCommand— so it can switch to a piped stdin write with a very small diff, no temp-file lifetime questions. For the elevated initial path, a temp file whose path is the argv (with the payload’s ACL restricted to the invoking user) fits within limits regardless of profile size.One more fix worth taking regardless of transport: expanding globs on the orchestrator side and shipping the concrete path list also bakes in a TOCTOU gap — the profile contents can change between enumeration and the helper applying ACLs. Passing the patterns + scan roots and letting the helper expand them in-process both bounds the payload (it becomes O(config), not O(filesystem)) and closes that drift. Your observed recovery-by-reorganizing (697 → 498 items) works precisely because the payload is O(filesystem) today.
Cheap guard for the interim: the spawn site already logs
payload_len; a pre-flight check (b64.len() > ~30_000→ fail with "sandbox setup payload exceeds the Windows command-line limit; N deny-list entries — see <doc>") would at least convert the opaqueos error 206into an actionable message, given your point that escalated commands silently bypass the helper and mask the failure.Regression test shape: construct a payload with ~1,000 synthetic deny paths and assert the chosen transport delivers it (stdin/temp-file round-trip), plus a unit assert that refresh-payload size is independent of the number of concrete glob matches once patterns-not-paths lands.
Additional datapoint from a different trigger path, same error class, on CLI 0.150.1 and 0.151.0-alpha.6 (npm install, Windows 11 26200,
[windows] sandbox = "elevated"):Session INIT (not per-command execution) fails deterministically on write-capable profiles with
failed to load AGENTS.md instructions for environment 'local': The filename or extension is too long. (os error 206), before the TUI ever appears. Read-only profiles on the same home are fine.Two observations that may help narrow the payload contributor:
CODEX_HOMEis long-established (since March) and itscap_sidhas accumulated 90 per-cwd/per-root sandbox SIDs (8,938 bytes); the sandbox log showssetup refresh: spawning codex-windows-sandbox-setup.exe (payload_len=9208)cycles completing, so the spawn that dies with 206 appears to be a later, unlogged helper invocation with a larger assembled command line.CODEX_HOMEunder a different path starts fine, but only because the clone silently runs a reduced sandbox flow (its.sandbox-binnever receives a command-runner), which masks the failure and makes fresh-home repro attempts misleading.Resetting the accumulated state (
cap_sidpruned to 6 live entries,.sandboxrenamed aside, both together) did NOT clear it here, so the payload contributor is something else that survives a state reset. Filed the startup-walk hang that was masking this as #41257; happy to provide full logs from this machine if useful.