[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

Status Closed — duplicate
Reported on v2.1.217
Maintainer reply None cached
Activity 6 comments · opened Jul 23, 2026 · closed Aug 15, 2026

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 as Xcode-beta.app, not selected)
  • Simulator runtimes present and healthy: iOS 18.5 / 26.5 / 27.0; simctl works 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-sim
  • EXC_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 getShaderCacheMainFoldercopyCacheMainFolder("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:

  1. 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 to mkdir there once.
  2. The recordBinaryArchiveUsage: path (triggered by CoreImage's precompiled uber-function binary archive during CIContext init) 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

  1. In claude-ios-sim.sb: allow file-write* on (subpath "<DARWIN_USER_CACHE_DIR>/com.anthropic.claude.ios-sim") (pass the resolved cache dir as a profile parameter like the existing DARWIN_TMP). Narrow, matches the profile's own narrowest-argument philosophy, and future-proofs against Metal writing shader caches from the helper.
  2. Alternatively (or additionally), the launcher (sidecarSandbox.ts?) could mkdir -p the helper's Metal cache tree before spawning — that is exactly the verified workaround above.
  3. Worth filing a Feedback to Apple as well: -[_MTLDevice recordBinaryArchiveUsage:] should nil-guard MTLGetShaderCachePath() — any sandboxed process that cannot create its cache dir will crash in CIContext init 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

  1. macOS 27 beta 4 (26A5388g) on Apple Silicon, Claude Desktop 1.24012.1.
  2. 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"

  1. Boot any iOS simulator (e.g. xcrun simctl boot <udid>).
  2. In Claude Desktop, open the iOS Simulator panel / let Claude Code attach to the simulator and grant device access.
  3. As soon as the panel starts the video stream, the helper process claude-ios-sim aborts (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

View original on GitHub ↗

5 Comments

robert-811 · 1 month ago

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 27A5228h selected, iOS 27.0 runtime 24A5380i.

Ran exactly the published command:

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"

$C_DIR/com.anthropic.claude.ios-sim did not exist beforehand, matching your prediction. After creating it and killing the helper so it respawned:

  • attach — works (as before)
  • screenshotworks, returns a live frame. Previously failed 100% of the time across ~20 attempts
  • tapworks, the app responded and switched tabs

No new crash reports since. This is the whole panel restored, not just the stream.

Two data points that may help scope it:

  • Your 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.metal and com.apple.metalfe — all no effect, because they're the wrong tree entirely. It is specifically $(getconf DARWIN_USER_CACHE_DIR), i.e. the C/ 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.sb granting file-write* on only CORESIM_HOME, CORESIM_LOGS, DARWIN_TMP and /private/var/tmp with no param for the Darwin user cache dir — but without your disassembly of getCacheMainFolder/MTLGetShaderCachePath I had the mechanism only as an unverified hypothesis and the wrong directory. Adding a DARWIN_CACHE param alongside the existing DARWIN_TMP looks like a one-line profile change.

For anyone on a newer Xcode 27 seed: you may hit #80180 / #79991 first — SimulatorKit.framework moved to Contents/SharedFrameworks, so attach fails before any Metal code runs, and this crash stays hidden until that's worked around too:

ln -s ../../SharedFrameworks /Applications/Xcode.app/Contents/Developer/Library/PrivateFrameworks

Both were needed here to get a working panel.

robert-811 · 1 month ago

Follow-up: I bisected the workaround, and only one of the three directories is actually required.

Method — after each change, pkill -f claude-ios-sim so the helper respawned, then a screenshot call:

| Directories present under $C_DIR/com.anthropic.claude.ios-sim/ | Result |
|---|---|
| (nothing — bundle dir absent) | crash |
| bundle dir only, no children | crash |
| com.apple.metal only | works |
| all three (com.apple.metal/archiveUsage.db, com.apple.metalfe, com.apple.gpuarchiver) | works |

So com.apple.metalfe, com.apple.gpuarchiver, and the archiveUsage.db leaf are not needed, and the minimal workaround collapses to:

mkdir -p "$(getconf DARWIN_USER_CACHE_DIR)/com.anthropic.claude.ios-sim/com.apple.metal"

Note the bundle dir alone crashing is the interesting row: it shows the failing mkdir is specifically copyCacheMainFolder("com.apple.metal", create=true) creating the com.apple.metal child, not the per-bundle dir itself. Matches your getCacheMainFolder trace exactly.

Metal never writes anything into it. After several successful attach/screenshot/tap cycles:

$ find "$C_DIR/com.anthropic.claude.ios-sim" -type f | wc -l
       0

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 grant file-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 -rf the bundle dir between trials — a stale com.apple.metal from an earlier successful run masks it, same as the caveat in your original repro.

gwe-mg · 1 month ago

Confirmed the minimal workaround on an independent machine — the single com.apple.metal directory is sufficient here too.

Environment: macOS 27.0 (26A5388g), xcode-select/Applications/Xcode.app (stable, SimulatorKit.framework present in PrivateFrameworks, so the #80180 symlink was not needed), iPhone 17 on iOS 27.0.

Method: /bin/rm -rf the bundle dir between trials (per your caveat), recreate the directory under test, pkill -f claude-ios-sim, then attach + 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.metal only | works — attach, screenshot, tap all 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 the com.apple.metalfe / com.apple.gpuarchiver / archiveUsage.db entries from the original workaround are redundant.

Two confirmations worth adding:

  • tap really actuates, it's not just the stream coming back. Tapping Settings on the home screen launched the app and the next screenshot showed the Settings root — so the whole input path is restored, not only the video frames.
  • Metal writes nothing into the directory. After several successful attach/screenshot/tap cycles, find "$C_DIR/com.anthropic.claude.ios-sim" -type f | wc -l0. Same as your finding. This makes launcher-side mkdir (suggested fix #2) sufficient on its own; the seatbelt profile does not need a new file-write* subtree at all.

Crash signature in the failing rows is unchanged: EXC_CRASH/SIGABRT, abort() called, __exceptionPreprocess in the last exception backtrace — matching the original report.

vgalagaev · 1 month ago

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

  • Claude Desktop 1.24012.9
  • macOS 27.0 beta 26A5353q (earlier seed than the ones above)
  • Xcode 26.6 selected (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 above
  • Simulator: iPhone 17 Pro on the iOS 26.5 runtime (iOS 27.0 24A5355p runtime 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:

mkdir -p "$(getconf DARWIN_USER_CACHE_DIR)/com.anthropic.claude.ios-sim/com.apple.metal"
pkill -f claude-ios-sim

After the helper respawned: attach succeeds, the live view streams real frames, and screenshot/tap work again.

JayMcW99 · 1 month ago

Confirming the minimal (single-directory) form of the workaround fixes it here too.

Environment:

  • macOS 27.0 beta (26A5388g)
  • Apple M5
  • Xcode 26.6 (17F113) selected via xcode-select; Xcode-beta.app also installed, not selected
  • Simulator: iPhone 17 Pro, freshly booted

Before 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:]CIKernelLibraryCIContext 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 pkill needed — the next attach picked it up immediately. Result: attach succeeded, screenshot returned live frames through a full simulator boot to the home screen, no new .ips since. Adds a fifth confirmation and a new chip (M5, not previously reported in this thread).

Showing cached comments. Read the full discussion on GitHub ↗