danger-full-access sandbox blocks Metal/GPU IOKit access, crashing MLX on macOS
Environment
- codex-cli 0.118.0
- macOS 26.4 (25E246), Apple M4 Pro
- Python 3.14.2, MLX 0.31.0
- Config:
sandbox = "danger-full-access",bypass-sandbox = true, CLI alias with--dangerously-bypass-approvals-and-sandbox
Description
When Codex spawns a Python subprocess that imports mlx.core (e.g., for local voice transcription via mlx-whisper), the process crashes with SIGABRT due to an uncaught NSRangeException:
*** -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty array
The crash occurs in mlx::core::metal::Device::Device() during Metal GPU device enumeration. Metal's MTLCopyAllDevices() returns an empty array because the Seatbelt sandbox profile restricts IOKit access.
Root Cause
The Seatbelt profile compiled into the Codex binary only allows:
(allow iokit-open
(iokit-registry-entry-class "RootDomainUserClient"))
This applies even in danger-full-access mode. Metal GPU enumeration requires IOKit access to GPU driver classes (e.g., AGXMetalG16X on Apple Silicon), which is not permitted.
Reproduction
Minimal reproduction without Codex, confirming the Seatbelt IOKit restriction is the root cause:
# This crashes with the identical NSRangeException:
sandbox-exec -p '(version 1)(allow default)(deny iokit-open)' \
python3 -c "import mlx.core"
# This works fine (full IOKit access):
sandbox-exec -p '(version 1)(allow default)' \
python3 -c "import mlx.core; print(mlx.core.default_device())"
Expected Behavior
danger-full-access mode (and --dangerously-bypass-approvals-and-sandbox) should grant unrestricted IOKit access, allowing Metal/GPU operations in child processes.
Suggested Fix
Add an unrestricted IOKit rule to the danger-full-access sandbox template:
(allow iokit-open)
Or, if a scoped approach is preferred:
(allow iokit-open
(iokit-registry-entry-class "RootDomainUserClient")
(iokit-registry-entry-class "AGXDeviceUserClient")
(iokit-registry-entry-class "IOGPUDeviceUserClient"))
Crash Report (abbreviated)
Process: Python
Parent: codex
Exception: EXC_CRASH (SIGABRT)
Reason: *** -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty array
Crash thread backtrace:
CoreFoundation -[__NSArray0 objectAtIndex:]
libmlx.dylib mlx::core::metal::Device::Device()
libmlx.dylib mlx::core::metal::device(mlx::core::Device)
libmlx.dylib mlx::core::metal::MetalAllocator::MetalAllocator()
libmlx.dylib mlx::core::allocator::allocator()
libmlx.dylib mlx::core::random::key(unsigned long long)
core.cpython-314-darwin.so [module init]
8 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Thanks for flagging. #16931 describes the same symptom (Metal invisible inside sandbox), but this issue adds the root cause analysis: the Seatbelt profile only allows
iokit-openforRootDomainUserClient, which blocks GPU driver classes needed by Metal. I also include a minimalsandbox-execreproduction and a concrete fix suggestion.Happy to close this if #16931 gets the fix merged, but keeping it open for now since it has more actionable detail.
the IOKit restriction goes deeper than just Metal device enumeration. CoreML model compilation (which WhisperKit, mlx-whisper, and most on-device inference frameworks trigger) also needs IOKit access to detect the Neural Engine and determine which execution providers to use. without it, the framework falls back to CPU-only inference, which is 5-10x slower than ANE on Apple Silicon.
we hit a related issue building local voice transcription. even after granting IOKit access, CoreML model compilation on first run spawns a background coremlcompiler process that itself needs IOKit and filesystem access to write the compiled .mlmodelc cache to the container's caches directory. if the sandbox restricts that write, you get a silent fallback to recompilation on every inference call, which pegs one core at 100% indefinitely.
the sandbox-exec reproduction is helpful. for anyone running into this before an upstream fix ships, you can pre-compile the MLX/CoreML model outside the sandbox and point the framework at the cached compiled version. this skips the IOKit-dependent compilation step entirely, though you still need the Metal device enumeration fix for actual GPU inference.
our on-device transcription service that handles WhisperKit CoreML model compilation caching, the ANE vs CPU fallback logic, and the pre-compiled model path approach I mentioned: https://github.com/m13v/fazm/blob/main/Desktop/Sources/TranscriptionService.swift
@YuancFeng thanks for reporting this! if you are in
danger-full-accessandapproval_policy=never, Codex should not execute code under seatbelt. It completely bypasses sandbox construction and seatbelt should not be invoked. Can you share your config.toml? do you have any other settings enforced via/etc/codex/requirements.toml?@viyatb-oai Relevant config only:
There is no
/etc/codex/requirements.tomlon this machine.So the remaining question seems to be why a Codex-spawned Python child is still running under Seatbelt in this mode. If useful, I can provide:
sysdiagnoseprivately rather than on the public issue.please do! 1-3 should be enough.
@viyatb-oai Here are the three pieces you asked for, in minimal form. Happy to share the full IPS crash report, the complete
turn_context, and the session rollout excerpt privately if any of it helps — just let me know your preferred channel (OpenAI email / internal ticket / etc.).TL;DR
With
config.tomlsettingsandbox = "danger-full-access"+approval_policy = "never"+bypass-sandbox = true+bypass-approvals = true, and the CLI launched viacodex --dangerously-bypass-approvals-and-sandbox, the live session'sturn_context.sandbox_policy.typestill resolves toworkspace-write, notdanger-full-access. That is why the child process still ends up under Seatbelt and hits the IOKit restriction described in the issue body. I think the config path worth tracing is how the user's--dangerously-bypass-approvals-and-sandbox+sandbox = "danger-full-access"get translated into the in-sessionsandbox_policy— something in that path is downgrading it toworkspace-write.No
/etc/codex/requirements.tomlon this machine. No project-levelAGENTS.mdoverride of sandbox / approval settings either.---
1. The command Codex issued inside the session
During a Codex TUI session working on a local project that wraps
mlx_whisper, Codex wanted to introspect themlx_whisper.transcribeAPI surface and issued anexec_commandtool call along the lines of:import mlx_whispertransitively importsmlx.core, which triggersMTLCopyAllDevices()during Metal device enumeration, which is where the crash happens.The exec tool call returned exit code
-1with theNSRangeException/-[__NSArray0 objectAtIndex:]message already quoted in the issue body. In the session log, theturn_contextrecorded for that same turn showsapproval_policy: "never"butsandbox_policy.type: "workspace-write"— that mismatch between the two is the actionable part.---
2. Isolating the denial — minimal sandbox-exec repro
I can't paste raw kernel Seatbelt denial lines without
sudo+ thesandboxsubsystem predicate, so the cleanest evidence I can give you is this deterministic A/B that isolates the trigger to exactlyiokit-open:The delta is one rule. This matches the
(allow iokit-open (iokit-registry-entry-class "RootDomainUserClient"))-only pattern I pulled from the Codex binary.If kernel-side
deny(1) iokit-openlines (i.e.log show --predicate 'sender == "Sandbox"'around a reproduction) would help, I can capture those undersudoand share privately.---
3. Crash — the frames that actually matter
Full IPS is available on request; here is the minimal stack that pinpoints the root cause. Relative offsets only, no base addresses, registers, or binary UUIDs — those don't add diagnostic value once symbols are present, and I'd rather not leave a full memory-layout fingerprint in a public thread.
Reading the stack: MLX's
metal::Deviceconstructor assumesMTLCopyAllDevices()returns a non-emptyNSArray. Under the Codex Seatbelt profile — which only allowsiokit-openfor theRootDomainUserClientclass — the GPU driver's user-client class is denied,MTLCopyAllDevices()returns__NSArray0, and the immediateobjectAtIndex:0raisesNSRangeExceptionthat the MLX side doesn't catch. The Metal driver bundle is loaded fine (it shows up in the image list), so this really is an IOKit-class policy problem, not a missing-driver problem.---
Environment (coarse)
Apple Silicon Mac, macOS 26.x, stock homebrew Python 3.14, recent MLX release. The crash reproduces on the minimal
python3 -c "import mlx.core"command above, so I don't think finer-grained versions are load-bearing — let me know if they are and I'll share the exact build info off-thread.