0.145.0: Windows sandbox setup re-runs ACL repair when writable-root rights are inherited-only; large %TEMP% makes spawn hang >60s (windows-sandbox tests deterministically time out)
Summary
At rust-v0.145.0, the in-repo test codex-windows-sandbox unified_exec::tests::legacy_non_tty_cmd_emits_output (and siblings sharing the legacy spawn path) deterministically times out (nextest 60s terminate) on a Windows dev machine. The same test passed in isolation at rust-v0.144.6 on the same machine three days earlier.
Root cause appears to be the new explicit-ACE verification in sandbox setup: writable roots whose rights come only from inherited ACEs are now treated as needing repair, and the repair propagates ACLs across the entire writable-root tree. With the user''s %TEMP% (a default writable root) containing ~219k files, that repair exceeds 60s, and the spawn path awaits setup with no timeout.
Deterministic reproduction
# Windows 11, non-elevated, developer mode on
$env:__COMPAT_LAYER = "RunAsInvoker"
cargo nextest run -E "test(legacy_non_tty_cmd_emits_output)" --no-capture
- 2 runs x 2 tries: 4/4 timed out at exactly 60s on an idle machine (CPU ~2%).
- Test checkpoint output:
cmd codex_home=...prints, butcmd spawn returnednever appears → the hang is insidespawn_windows_sandbox_session_legacy(...), before process creation; the test''s own 5s command / 10s collect timeouts are never reached because spawn itself blocks. - Leftover sandbox log in the temp
CODEX_HOMEcontains only theSTART: C:\Windows\System32\cmd.exe /c echo LEGACY-NONTTY-CMDline;cap_sidwas written → hang occurs after capability-SID creation, during setup.
Control experiment
Same test with TMP/TEMP redirected to a freshly created empty directory: PASS in 11.7s (all checkpoints fire).
The user %TEMP% on this machine: 218,980 files / 6.9 GB — realistic for a long-lived dev box.
Code-level analysis
acl.rsin 0.145.0 addsAceScope::Explicit/path_mask_has_explicit_allow_acewith the rationale: "SET_ACCESS cannot replace an ACE inherited from an ancestor, so it cannot make an explicit-only repair converge when that inherited ACE contains stale rights."setup_main(thecodex-windows-sandbox-setupbinary) usespath_mask_has_explicit_allow_aceto decide whether a writable root needs repair. Roots whose rights are inherited-only (the normal state of%TEMP%and most user directories) now always trigger repair.- Repair cost scales with tree size (see also #33158). For fresh
CODEX_HOMEs (new capability SIDs — every test run, and every new install/home), repair re-triggers from scratch. spawn_windows_sandbox_session_legacyawaits setup with no timeout, so callers observe an indefinite hang rather than an error.
Impact
- Test suite:
codex-windows-sandboxlegacy tests fail deterministically on dev machines with real-world%TEMP%sizes (they pass on clean CI temp dirs), which makes local baseline comparison noisy for anyone developing on Windows. - Product: the first sandboxed exec for a given
CODEX_HOMEon such machines stalls for minutes with no feedback. This looks like the same family as user reports #32477 / #33158, but 0.145.0''s inherited-ACE handling re-triggers the cost even where effective rights were already sufficient.
Environment
- Windows 11 Pro for Workstations 10.0.26200, non-elevated user, developer mode on
rust-v0.145.0tag, debug build, cargo-nextest default profile,__COMPAT_LAYER=RunAsInvoker- Passed at
rust-v0.144.6in the same environment (isolated run, 2026-07-19)
Possible directions
- Accept inherited allow ACEs when the effective rights already match (pre-0.145.0 behavior), reserving explicit repair for genuinely stale/insufficient rights;
- Or scope the repair to the root directory with inheritable ACEs instead of per-node propagation;
- Or make setup asynchronous/bounded with a clear log line, and add a timeout to the spawn path so failures surface as errors instead of indefinite hangs.
Related: #33158 (setup scales poorly with writable-root size), #32477 (user-facing 40-60s stalls on 0.144.1).
5 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Same here. Cleaning
%TEMP%appears to resolve the slowdown.Before cleanup:
%TEMP%: 282,365 files / 79.94 GiBread ACL run completedandsetup refresh: processed 3 write roots ... errors=[]After cleanup:
%TEMP%: 17,002 files / 12.32 GiBchild_process: working withoutEPERMapply_patchcreate/delete: 1.6–2.2 secondsThe sandbox configuration remained
[windows] sandbox = "elevated".One caveat: Codex Desktop also auto-updated between the measurements, from MSIX package
26.715.10079.0to26.721.3996.0, so this is not a perfectly isolated A/B test. However, the before/after timings and the very large reduction in%TEMP%file count strongly support the writable-root ACL traversal diagnosis described in this issue.Additional real-world data point from a native Windows machine (Codex CLI 0.146.0):
Observed state
%USERPROFILE%\.codex\cap_sidcontainedworkspace_by_cwd=31andwritable_root_by_path=158(19,665 bytes).cmd.exe /c echofailed with:SetTokenInformation(TokenDefaultDacl) failed: 1344(Windows: “No more memory is available to update security information.”)
workspace-writepath and withwindows.sandbox="unelevated", so it was not specific to a custom permission profile or network policy.Reset/rebuild behavior
cap_sidaside as a backup (no auth files were touched) and retried.writable_root_by_pathentries.codex-windows-sandbox-setup.exethen used roughly one CPU core continuously for more than six minutes.applied deny ACE to protect %USERPROFILE%\.codexabout every 7–8 seconds.Product impact
I eventually selected
danger-full-access+approval_policy="never"with the unelevated fallback because safe mode imposed multi-minute opaque startup work and repeated authorization friction.This is an important security UX failure mode: when the safe default is slow or blocks routine work, non-expert users are incentivized to disable the sandbox entirely.
Potential improvements:
cap_sidgrowth and avoid placing every historical path capability SID in a new token DACL.No SID values, credentials, or private paths are included here.
— Cola1018
Additional real-world confirmation: in my case, two custom
uvwritable roots were enough to make sandboxed terminal commands appear to run indefinitely, and removing those roots restored normal command execution.Environment
10.0.26200)26.730.61309OpenAI.Codex_26.730.7989.0OpenAI.Codex_26.727.6591.0codex-command-runner-0.146.0-alpha.9.2.exesandbox_mode = "workspace-write",approval_policy = "on-request", and[windows] sandbox = "unelevated"Symptom
The problem started suddenly on August 4 without any change to
config.toml. In every permission mode except Full Access, even trivial terminal commands stayed in the UI as running indefinitely. Full Access worked normally.During affected runs, the sandbox log showed:
but there was no corresponding
setup binary completedor commandSUCCESSentry.Custom writable roots and A/B result
My configuration contained:
%TEMP%itself was only about 5,840 files / 4.48 GiB and enumerated in about 1.3 seconds. By contrast, a recursive enumeration that proceeded into the firstuvroot did not finish within a 60-second diagnostic timeout.After removing only these two
writable_rootsentries, terminal commands began succeeding again in the sandboxed/non-Full-Access permission modes. I did not need to reinstall Codex or change PowerShell.I originally added these roots so Codex could directly launch or run scripts/tools using
uv-managed dependencies. I no longer remember which specific tool first required it, so that historical reason is approximate, but the configuration change and recovery result above are confirmed.This appears consistent with the issue's diagnosis, with large custom writable roots—not only
%TEMP%—triggering the expensive ACL refresh path.Retested on
rust-v0.147.0(be6e8eac02). Not fixed — plus new timing data that refines the attribution from my original report.Code side: the explicit-ACE gate is still in place in 0.147.0 (
acl.rsskips inherited-only ACEs viaAceScope::Explicit, reached throughpath_write_aces_need_refresh→dacl_allow_mask_needs_refresh, which still triggers the full-tree ACL refresh in setup). The only commit touchingacl.rsbetween v0.145.0 and v0.147.0 (a26f219f67, elevated-startup hardening) does not change this trigger.Four-arm timing for
unified_exec::legacy_non_tty_cmd_emits_output(same machine as the original report; debug build, single test, 60s budget):| Arm | writable-root tree | Result |
|---|---|---|
| A. real
%TEMP%| 111,207 files + 53,810 dirs = 165,017 objects, 10 levels deep | timeout 60s (×2 tries) || B. empty dir | 0 objects | PASS 7.6s |
| C. synthetic, flat | ~200,200 objects, 1 level deep | PASS 43.5s |
| D. synthetic, dir-dense | ~40,200 objects, 2 levels deep | PASS 30.8s |
Two takeaways:
%TEMP%(165k objects) times out while a larger flat synthetic tree (200k objects) passes. Real trees carry per-object structural multipliers (depth, in-use handles, restricted subtrees). Correcting my own earlier framing ("~219k files"): the determining factor is total tree cost = scale × structure, so repro attempts built on flat synthetic trees will significantly underestimate what real machines hit.The workaround from the original report still holds on 0.147.0: pointing
TMP/TEMPat a clean directory makes the same test pass in seconds.