[BUG] iOS Simulator panel helper (claude-ios-sim) crashes on launch on macOS 27 beta — seatbelt profile blocks Metal's new per-bundle shader-cache directory
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Summary
On macOS 27 beta, the claude-ios-sim helper aborts with an uncaught NSInvalidArgumentException every time the simulator panel starts its video stream. Root cause is an interaction between the helper's seatbelt profile (claude-ios-sim.sb) and two Metal changes new in the macOS 27 beta: the shader cache moved to a per-bundle-ID directory under DARWIN_USER_CACHE_DIR, and a new -[_MTLDevice recordBinaryArchiveUsage:] code path passes the shader-cache path into +[NSArray arrayWithObjects:count:] without a nil check. The sandbox denies creating the cache directory → MTLGetShaderCachePath() is nil → Metal throws → the helper dies. 100% reproducible; the panel never comes up.
Fully diagnosed with a minimal repro and a verified user-side workaround (below). Suggested fix: allow file-write* on the helper's Metal cache subtree in claude-ios-sim.sb.
Environment
- macOS 27.0 beta 4 (build
26A5388g), Apple Silicon (M3 Max) - Claude Desktop 1.24012.1
- Active Xcode via
xcode-select: Xcode 26.6 (17F113) at/Applications/Xcode.app(Xcode 27 beta 4 also installed asXcode-beta.app, not selected) - Simulator runtimes present and healthy: iOS 18.5 / 26.5 / 27.0;
simctlworks normally - Not reproducible on release macOS 26.x (the crashing Metal code path is new in the 27 beta)
Symptom
Opening the simulator panel (attach / launch) kills the helper within seconds, every time. Crash reports accumulate under ~/Library/Logs/DiagnosticReports[/Retired]/claude-ios-sim-*.ips (10 in ~20 minutes of attempts):
bundleID: com.anthropic.claude.ios-simEXC_CRASH (SIGABRT),abort() called, uncaught NSException
lastExceptionBacktrace (identical in all reports):
2 CoreFoundation -[__NSPlaceholderArray initWithObjects:count:] + 640 <- throws: nil object
3 CoreFoundation +[NSArray arrayWithObjects:count:] + 40
4 Metal __39-[_MTLDevice recordBinaryArchiveUsage:]_block_invoke + 132
7 Metal -[_MTLDevice recordBinaryArchiveUsage:] + 356
8 Metal -[_MTLBinaryArchive loadFromURL:error:] + 1504
9 Metal -[_MTLBinaryArchive initWithOptions:device:url:error:] + 916
10 CoreImage +[CIKernelLibrary(Internal) internalBinaryArchiveWithName:device:] + 156
11 CoreImage CI::PrecompiledUberFunctions::PrecompiledUberFunctions(CI::MetalContext const*) + 188
13 CoreImage CI::MetalContext::init(id<MTLCommandQueue>, char const*) + 472
16 CoreImage -[CIContext initWithMTLDevice:options:] + 100
18-21 FBSimulatorControl (frame-conversion pipeline of the video stream)
Root cause
Disassembly of Metal (macOS 27 beta 4) shows the crashing block builds a two-element array {MTLGetShaderCachePath(), @"archiveUsage.db"} to open an archive-usage LMDB. MTLGetShaderCachePath() resolves via getShaderCacheMainFolder → copyCacheMainFolder("com.apple.metal", create=true) → getCacheMainFolder, which does confstr(_CS_DARWIN_USER_CACHE_DIR) + stat + mkdir — and returns NULL if the directory cannot be created. The nil path then flows unguarded into arrayWithObjects:count:.
Two things changed in the macOS 27 beta:
- The shader cache is now per bundle ID:
$(getconf DARWIN_USER_CACHE_DIR)/<bundleID>/com.apple.metal/...— so every process with a fresh bundle ID must be able tomkdirthere once. - The
recordBinaryArchiveUsage:path (triggered by CoreImage's precompiled uber-function binary archive duringCIContextinit) is new and has no nil guard.
Meanwhile claude-ios-sim.sb (deny-by-default) only allows file-write* on CORESIM_HOME, CORESIM_LOGS, DARWIN_TMP (the T/ folder) and /private/var/tmp. The Darwin user cache dir (C/ folder) is not writable, the helper cannot create C/com.anthropic.claude.ios-sim/, MTLGetShaderCachePath() is nil, and the first CIContext created by FBSimulatorControl's video pipeline aborts the process.
Note the profile header says rules were validated per macOS release and asks for re-validation "after an Xcode or macOS bump" — this is exactly that case.
Minimal repro (no Claude involved)
// main.m — clang -fmodules -fobjc-arc main.m -o testbin
@import Foundation; @import Metal; @import CoreImage;
int main() {
id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
CIContext *ctx = [CIContext contextWithMTLDevice:dev]; // <- aborts here
NSLog(@"ok %@", ctx);
return 0;
}
Wrap it in a minimal .app bundle with a bundle ID that has no existing cache dir, then run it under the shipped profile:
/usr/bin/sandbox-exec -f /Applications/Claude.app/Contents/Resources/claude-ios-sim.sb \
-D "HELPER_BUNDLE=/path/to/Test.app" -D "XCODE_APP=/Applications/Xcode.app" \
-D "USER_HOME=$HOME" -D "CORESIM_HOME=$HOME/Library/Developer/CoreSimulator" \
-D "CORESIM_LOGS=$HOME/Library/Logs/CoreSimulator" \
-D "DARWIN_TMP=$(getconf DARWIN_USER_TEMP_DIR)" \
/path/to/Test.app/Contents/MacOS/Test
Result: libc++abi: terminating due to uncaught exception of type NSException with the identical Metal/CoreImage stack. Unsandboxed, or with the cache dir pre-created, the same binary runs fine. (Repro caveat: the crash only fires while C/<bundleID>/ doesn't exist yet — any unsandboxed run of the same bundle ID creates it and masks the bug afterwards.)
Verified user-side workaround
Pre-creating the directories from outside the sandbox is sufficient — Metal handles unwritable cache files gracefully; only the missing directory is fatal:
C_DIR=$(getconf DARWIN_USER_CACHE_DIR)
mkdir -p "$C_DIR/com.anthropic.claude.ios-sim/com.apple.metal/archiveUsage.db" \
"$C_DIR/com.anthropic.claude.ios-sim/com.apple.metalfe" \
"$C_DIR/com.anthropic.claude.ios-sim/com.apple.gpuarchiver"
After this, the panel attaches, streams video, and screenshots/taps work normally on macOS 27 beta 4. Caveat: the workaround does not survive a /var/folders cleanup.
What Should Happen?
Suggested fixes
- In
claude-ios-sim.sb: allowfile-write*on(subpath "<DARWIN_USER_CACHE_DIR>/com.anthropic.claude.ios-sim")(pass the resolved cache dir as a profile parameter like the existingDARWIN_TMP). Narrow, matches the profile's own narrowest-argument philosophy, and future-proofs against Metal writing shader caches from the helper. - Alternatively (or additionally), the launcher (
sidecarSandbox.ts?) couldmkdir -pthe helper's Metal cache tree before spawning — that is exactly the verified workaround above. - Worth filing a Feedback to Apple as well:
-[_MTLDevice recordBinaryArchiveUsage:]should nil-guardMTLGetShaderCachePath()— any sandboxed process that cannot create its cache dir will crash inCIContextinit on macOS 27 beta.
Error Messages/Logs
## Attachments available on request
- Full `.ips` crash reports (`claude-ios-sim-2026-07-22-*.ips`, 10x identical)
- Disassembly excerpts of `__39-[_MTLDevice recordBinaryArchiveUsage:]_block_invoke`, `__MTLGetShaderCachePath_block_invoke`, `getCacheMainFolder` (macOS 27 beta 4)
Steps to Reproduce
- macOS 27 beta 4 (26A5388g) on Apple Silicon, Claude Desktop 1.24012.1.
- Make sure the helper's Metal cache dir does NOT exist yet (fresh state, default on this OS):
ls "$(getconf DARWIN_USER_CACHE_DIR)/com.anthropic.claude.ios-sim" -> "No such file or directory"
- Boot any iOS simulator (e.g.
xcrun simctl boot <udid>). - In Claude Desktop, open the iOS Simulator panel / let Claude Code attach to the simulator and grant device access.
- As soon as the panel starts the video stream, the helper process
claude-ios-simaborts (SIGABRT, uncaught NSInvalidArgumentException). Crash report appears in ~/Library/Logs/DiagnosticReports/claude-ios-sim-*.ips. Reproduces on every attempt.
Claude Model
Other
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.217
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Warp
Additional Information
Claude Desktop 1.24012.1
Claude Code 2.1.217 (integrated)
Agent SDK 0.3.217
Showing cached comments. Read the full discussion on GitHub ↗
5 Comments
Confirmed — this workaround fully fixes it on a fourth machine. Outstanding diagnosis; the per-bundle-ID cache directory being new in the 27 beta is the piece everyone else (myself included) was missing.
Environment: Claude Desktop 1.24012.1, macOS 27.0 beta (26A5388g), M4 Pro, Xcode 27 beta
27A5228hselected, iOS 27.0 runtime24A5380i.Ran exactly the published command:
$C_DIR/com.anthropic.claude.ios-simdid not exist beforehand, matching your prediction. After creating it and killing the helper so it respawned:attach— works (as before)screenshot— works, returns a live frame. Previously failed 100% of the time across ~20 attemptstap— works, the app responded and switched tabsNo new crash reports since. This is the whole panel restored, not just the stream.
Two data points that may help scope it:
C/<bundleID>/non-existence caveat is important. I had already tried precreating~/Library/Caches/com.anthropic.claude.ios-sim/com.apple.metal,~/Library/Caches/com.apple.metalandcom.apple.metalfe— all no effect, because they're the wrong tree entirely. It is specifically$(getconf DARWIN_USER_CACHE_DIR), i.e. theC/folder, not~/Library/Caches. Worth stressing for anyone skimming, since the two are easy to conflate.$C_DIR/com.apple.metal(the non-per-bundle path) already existed here with populated per-GPU subdirs — so the presence of the old-style cache is not sufficient and doesn't mask the bug.Also confirms your suggested fix #1 is the right shape: I'd independently narrowed it to
claude-ios-sim.sbgrantingfile-write*on onlyCORESIM_HOME,CORESIM_LOGS,DARWIN_TMPand/private/var/tmpwith no param for the Darwin user cache dir — but without your disassembly ofgetCacheMainFolder/MTLGetShaderCachePathI had the mechanism only as an unverified hypothesis and the wrong directory. Adding aDARWIN_CACHEparam alongside the existingDARWIN_TMPlooks like a one-line profile change.For anyone on a newer Xcode 27 seed: you may hit #80180 / #79991 first —
SimulatorKit.frameworkmoved toContents/SharedFrameworks, soattachfails before any Metal code runs, and this crash stays hidden until that's worked around too:Both were needed here to get a working panel.
Follow-up: I bisected the workaround, and only one of the three directories is actually required.
Method — after each change,
pkill -f claude-ios-simso the helper respawned, then ascreenshotcall:| Directories present under
$C_DIR/com.anthropic.claude.ios-sim/| Result ||---|---|
| (nothing — bundle dir absent) | crash |
| bundle dir only, no children | crash |
|
com.apple.metalonly | works || all three (
com.apple.metal/archiveUsage.db,com.apple.metalfe,com.apple.gpuarchiver) | works |So
com.apple.metalfe,com.apple.gpuarchiver, and thearchiveUsage.dbleaf are not needed, and the minimal workaround collapses to:Note the bundle dir alone crashing is the interesting row: it shows the failing
mkdiris specificallycopyCacheMainFolder("com.apple.metal", create=true)creating thecom.apple.metalchild, not the per-bundle dir itself. Matches yourgetCacheMainFoldertrace exactly.Metal never writes anything into it. After several successful attach/screenshot/tap cycles:
The directory stays completely empty. That confirms your "Metal handles unwritable cache files gracefully; only the missing directory is fatal" point — writes are still denied by the profile and nothing degrades. Useful for scoping the real fix: the helper doesn't need write access to the cache tree at all in practice, so suggested fix #2 (launcher
mkdir -ps the directory before spawning) is sufficient on its own and is strictly narrower than widening the seatbelt profile. If you'd rather not grantfile-write*on a new subtree, you don't have to.One caveat for anyone reproducing the bisect: the crash only reappears while the directory is genuinely absent, so
rm -rfthe bundle dir between trials — a stalecom.apple.metalfrom an earlier successful run masks it, same as the caveat in your original repro.Confirmed the minimal workaround on an independent machine — the single
com.apple.metaldirectory is sufficient here too.Environment: macOS 27.0 (26A5388g),
xcode-select→/Applications/Xcode.app(stable,SimulatorKit.frameworkpresent inPrivateFrameworks, so the #80180 symlink was not needed), iPhone 17 on iOS 27.0.Method:
/bin/rm -rfthe bundle dir between trials (per your caveat), recreate the directory under test,pkill -f claude-ios-sim, thenattach+screenshot.| State of
$C_DIR/com.anthropic.claude.ios-sim/| Result ||---|---|
| (bundle dir absent) | crash — reproduced 3× |
| bundle dir only, no children | crash |
|
com.apple.metalonly | works —attach,screenshot,tapall fine |So
mkdir -p "$(getconf DARWIN_USER_CACHE_DIR)/com.anthropic.claude.ios-sim/com.apple.metal"is the whole fix on this machine, and thecom.apple.metalfe/com.apple.gpuarchiver/archiveUsage.dbentries from the original workaround are redundant.Two confirmations worth adding:
tapreally actuates, it's not just the stream coming back. Tapping Settings on the home screen launched the app and the nextscreenshotshowed the Settings root — so the whole input path is restored, not only the video frames.find "$C_DIR/com.anthropic.claude.ios-sim" -type f | wc -l→0. Same as your finding. This makes launcher-sidemkdir(suggested fix #2) sufficient on its own; the seatbelt profile does not need a newfile-write*subtree at all.Crash signature in the failing rows is unchanged:
EXC_CRASH/SIGABRT,abort() called,__exceptionPreprocessin the last exception backtrace — matching the original report.Confirming on another machine — and on app 1.24012.9, newer than the builds reported above, so this is still present in the latest desktop build. The minimal workaround alone fully restores the panel.
Environment
xcode-select -p→/Applications/Xcode.app); Xcode 27.0 beta installed but not selected — no SimulatorKit symlink needed here, consistent with the seed-scoping note above24A5355pruntime also installed)Symptom as it appears in the UI (for anyone searching): the panel loops "Attach simulator" → device list → black frame → back to the attach screen, indefinitely. First observed 2026-07-27; control-plane MCP calls were also unreliable across sessions (panel died mid-session and would not re-attach).
Applied only the minimal form:
After the helper respawned: attach succeeds, the live view streams real frames, and screenshot/tap work again.
Confirming the minimal (single-directory) form of the workaround fixes it here too.
Environment:
xcode-select; Xcode-beta.app also installed, not selectedBefore applying anything, I had a crash report from earlier today (
claude-ios-sim-2026-07-28-123454.ips) with the identical signature —EXC_CRASH/SIGABRT,abort(), and the same__NSPlaceholderArray initWithObjects:count:→+[NSArray arrayWithObjects:count:]→-[_MTLDevice recordBinaryArchiveUsage:]_block_invoke→-[_MTLBinaryArchive loadFromURL:error:]→CIKernelLibrary→CIContext initWithMTLDevice:options:chain reported above.Applied only the bisected minimal form:
``
bash``mkdir -p "$(getconf DARWIN_USER_CACHE_DIR)/com.anthropic.claude.ios-sim/com.apple.metal"
No
pkillneeded — the nextattachpicked it up immediately. Result:attachsucceeded,screenshotreturned live frames through a full simulator boot to the home screen, no new.ipssince. Adds a fifth confirmation and a new chip (M5, not previously reported in this thread).