macOS seatbelt blocks com.apple.FSEvents, causing directory fs.watch/Watchpack failures in sandboxed execution

Open 💬 2 comments Opened Mar 24, 2026 by ystory

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/project

Actual 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.ts

Output:

watch-ok

This appears to be specific to directory watching, not to fs.watch() in general.

What steps can reproduce the bug?

  1. 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
  1. Observe that sandboxed directory watching fails with EMFILE.
  1. Observe that --log-denials reports mach-lookup com.apple.FSEvents.
  1. 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.ts changes 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=true avoids the issue for Next/Watchpack-based stacks, but it is only a workaround.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗