Skip to content

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

@ystory

Description

@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.

  2. Observe that --log-denials reports mach-lookup com.apple.FSEvents.

  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsandboxIssues related to permissions or sandboxing

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions