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)

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

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, but cmd spawn returned never appears → the hang is inside spawn_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_HOME contains only the START: C:\Windows\System32\cmd.exe /c echo LEGACY-NONTTY-CMD line; cap_sid was 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.rs in 0.145.0 adds AceScope::Explicit / path_mask_has_explicit_allow_ace with 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 (the codex-windows-sandbox-setup binary) uses path_mask_has_explicit_allow_ace to 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_legacy awaits setup with no timeout, so callers observe an indefinite hang rather than an error.

Impact

  • Test suite: codex-windows-sandbox legacy 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_HOME on 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.0 tag, debug build, cargo-nextest default profile, __COMPAT_LAYER=RunAsInvoker
  • Passed at rust-v0.144.6 in 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).

View original on GitHub ↗

5 Comments

github-actions[bot] contributor · 1 month ago

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

  • #34529
  • #33737

Powered by Codex Action

nicemd · 1 month ago

Same here. Cleaning %TEMP% appears to resolve the slowdown.

Before cleanup:

  • %TEMP%: 282,365 files / 79.94 GiB
  • Elevated Codex Desktop sandbox refresh: consistently 89–98 seconds
  • The delay occurred between read ACL run completed and setup refresh: processed 3 write roots ... errors=[]

After cleanup:

  • %TEMP%: 17,002 files / 12.32 GiB
  • Sandbox refresh: approximately 40–55 ms
  • Minimal PowerShell tool call: 1.1 seconds end-to-end
  • Node child_process: working without EPERM
  • Warm apply_patch create/delete: 1.6–2.2 seconds

The sandbox configuration remained [windows] sandbox = "elevated".

One caveat: Codex Desktop also auto-updated between the measurements, from MSIX package 26.715.10079.0 to 26.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.

Cola1018 · 26 days ago

Additional real-world data point from a native Windows machine (Codex CLI 0.146.0):

Observed state

  • A long-lived %USERPROFILE%\.codex\cap_sid contained workspace_by_cwd=31 and writable_root_by_path=158 (19,665 bytes).
  • A minimal sandboxed cmd.exe /c echo failed with:

SetTokenInformation(TokenDefaultDacl) failed: 1344
(Windows: “No more memory is available to update security information.”)

  • The failure reproduced with the legacy workspace-write path and with windows.sandbox="unelevated", so it was not specific to a custom permission profile or network policy.

Reset/rebuild behavior

  • I moved cap_sid aside as a backup (no auth files were touched) and retried.
  • Codex immediately regenerated 118 writable_root_by_path entries.
  • codex-windows-sandbox-setup.exe then used roughly one CPU core continuously for more than six minutes.
  • The sandbox log repeatedly appended applied deny ACE to protect %USERPROFILE%\.codex about every 7–8 seconds.
  • The parent probe timed out after 64 seconds, but the setup helper remained alive and continued ACL work as an orphan until it was manually stopped.

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:

  1. Bound or compact cap_sid growth and avoid placing every historical path capability SID in a new token DACL.
  2. Avoid recursive per-node ACL repair for broad writable roots; prefer root-level inheritable ACEs or incremental repair.
  3. Make setup cancellable and ensure timed-out parents terminate their helper.
  4. Surface progress and the exact root being processed.
  5. Prefer capability-based protections for irreversible operations and credential use, while keeping routine reversible file/network work low-friction.

No SID values, credentials, or private paths are included here.

— Cola1018

leeeezx · 23 days ago

Additional real-world confirmation: in my case, two custom uv writable roots were enough to make sandboxed terminal commands appear to run indefinitely, and removing those roots restored normal command execution.

Environment

  • Windows 11 x64 (10.0.26200)
  • Codex Desktop About version shown in the UI: 26.730.61309
  • Installed MSIX package after today's update: OpenAI.Codex_26.730.7989.0
  • Previous package recorded in the sandbox log on August 4: OpenAI.Codex_26.727.6591.0
  • Sandbox log identified codex-command-runner-0.146.0-alpha.9.2.exe
  • Legacy configuration path: sandbox_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:

START: ...\pwsh.exe ...
setup refresh: spawning ...\codex-windows-sandbox-setup.exe
...
read ACL run completed

but there was no corresponding setup binary completed or command SUCCESS entry.

Custom writable roots and A/B result

My configuration contained:

[sandbox_workspace_write]
network_access = true
writable_roots = [
    "C:\\Users\\<user>\\AppData\\Local\\uv",
    "C:\\Users\\<user>\\AppData\\Roaming\\uv",
]

%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 first uv root did not finish within a 60-second diagnostic timeout.

After removing only these two writable_roots entries, 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.

15230745073 · 18 days ago

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.rs skips inherited-only ACEs via AceScope::Explicit, reached through path_write_aces_need_refreshdacl_allow_mask_needs_refresh, which still triggers the full-tree ACL refresh in setup). The only commit touching acl.rs between 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:

  1. The cost is a continuous curve in tree size (0 → 7.6s, 40k → 30.8s, 200k → 43.5s), not a threshold — any sufficiently large writable root will eventually cross whatever timeout sits above the setup path.
  2. Object count alone underestimates the real cost. The real %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/TEMP at a clean directory makes the same test pass in seconds.