-
Notifications
You must be signed in to change notification settings - Fork 10.2k
macOS seatbelt blocks com.apple.FSEvents, causing directory fs.watch/Watchpack failures in sandboxed execution #15698
Description
What version of Codex CLI is running?
codex-cli 0.117.0-alpha.10
What subscription do you have?
Pro
Which model were you using?
gpt-5.4
What platform is your computer?
Darwin 25.3.0 arm64 arm
What terminal emulator and version are you using (if applicable)?
Codex Desktop app (embedded runtime; TERM=dumb, SHELL=/bin/zsh).
Also reproducible via the public CLI using `codex sandbox macos --full-auto`.
What issue are you seeing?
On macOS, Codex's sandboxed execution path appears to deny Mach lookups to com.apple.FSEvents, which causes directory fs.watch() to fail with:
EMFILE: too many open files, watch
In real projects, this can surface as repeated Watchpack/Next.js dev-server failures, including apparent false-positive next.config.ts change detection and unnecessary restarts.
I can reproduce this outside the app with the public CLI sandbox command:
codex sandbox macos --full-auto --log-denials -- /opt/homebrew/bin/node -e 'const fs=require("fs"); const dir=process.argv[1]; const w=fs.watch(dir); let done=false; w.on("error", err=>{ if(done) return; done=true; console.error("watch-error", err.code, err.message); process.exit(1); }); setTimeout(()=>{ if(done) return; done=true; w.close(); console.log("watch-ok"); process.exit(0); }, 1500);' /Users/<redacted>/path/to/projectActual output:
watch-error EMFILE EMFILE: too many open files, watch
=== Sandbox denials ===
(node) mach-lookup com.apple.FSEvents
(node) mach-lookup com.apple.logd
(node) mach-lookup com.apple.system.notification_center
As a control, file watching succeeds under the same sandboxed execution path:
codex sandbox macos --full-auto --log-denials -- /opt/homebrew/bin/node -e 'const fs=require("fs"); const target=process.argv[1]; const w=fs.watch(target); let done=false; w.on("error", err=>{ if(done) return; done=true; console.error("watch-error", err.code, err.message); process.exit(1); }); setTimeout(()=>{ if(done) return; done=true; w.close(); console.log("watch-ok"); process.exit(0); }, 1500);' /Users/<redacted>/path/to/project/next.config.tsOutput:
watch-ok
This appears to be specific to directory watching, not to fs.watch() in general.
What steps can reproduce the bug?
- On macOS, run:
codex sandbox macos --full-auto --log-denials -- /opt/homebrew/bin/node -e 'const fs=require("fs"); const dir=process.argv[1]; const w=fs.watch(dir); let done=false; w.on("error", err=>{ if(done) return; done=true; console.error("watch-error", err.code, err.message); process.exit(1); }); setTimeout(()=>{ if(done) return; done=true; w.close(); console.log("watch-ok"); process.exit(0); }, 1500);' /path/to/a/readable/project-directory-
Observe that sandboxed directory watching fails with
EMFILE. -
Observe that
--log-denialsreportsmach-lookup com.apple.FSEvents. -
As a control, run the same script against a file path instead of a directory path and observe that it succeeds.
This also reproduces in a real Next.js project:
-
pnpm dev -
pnpm dev --webpack
Inside the Codex sandbox, Watchpack repeatedly reports EMFILE, and Next may falsely detect next.config.ts changes and restart the dev server.
What is the expected behavior?
Directory fs.watch() should work for readable project paths in Codex's macOS sandboxed execution environment.
As a result:
-
standalone directory watchers should not fail with
EMFILE -
Watchpack should not emit repeated watcher errors
-
Next.js dev should not falsely detect
next.config.tschanges or restart due to sandbox-induced watch failures
Additional information
I investigated this locally and found that the current macOS seatbelt base policy appears to be missing the following rule:
(allow mach-lookup
(global-name "com.apple.FSEvents")
)Adding that rule to the seatbelt base policy fixed both:
-
standalone sandboxed directory
fs.watch()repros -
minimal Watchpack repros
It does not appear to bypass path restrictions on its own. With that rule added, directory watching succeeds for explicitly readable roots, while unrelated paths outside those roots still fail with EPERM.
This looks like a sandbox policy bug or sandbox compatibility bug in Codex, rather than a generic Node/Watchpack RLIMIT_NOFILE issue.
Workaround:
WATCHPACK_POLLING=trueavoids the issue for Next/Watchpack-based stacks, but it is only a workaround.