[Bug] iOS Simulator helper (claude-ios-sim) crash-loops on macOS 27 beta — uncaught NSException in Metal recordBinaryArchiveUsage via CoreImage

Status Closed — duplicate
Maintainer reply None cached
Activity 6 comments · opened Jul 31, 2026 · closed Aug 15, 2026

Environment

  • Claude desktop app: 1.24012.9 (macOS)
  • Helper: /Applications/Claude.app/Contents/Helpers/Claude iOS Sim.app/Contents/MacOS/claude-ios-sim (bundle id com.anthropic.claude.ios-sim; no CFBundleShortVersionString set)
  • macOS: 27.0 beta, build 26A5388g
  • Xcode: 26.6 (17F113), default toolchain at /Applications/Xcode.app/Contents/Developer
  • Hardware: Apple Silicon (arm64) MacBook Pro
  • Simulators: iOS 27.0 runtime, default device set; multiple devices booted and healthy

Symptom

The Claude Code iOS Simulator integration is unusable: every interaction that spawns the helper (attach live panel, and headless screenshot/input actions) crashes it within seconds. The MCP server then reports "Claude Code iOS Simulator is restarting after a crash" and, after retries, "hit an internal error." Four crash reports were produced within minutes during normal use.

Additional data point: with devices booted in the default device set (verified via xcrun simctl list devices booted and the device directory under ~/Library/Developer/CoreSimulator/Devices/), attach with no arguments reported "No booted simulator found"; attach with an explicit udid succeeded (panel opened, then the helper crashed seconds later). Possibly a separate, minor device-discovery bug — mentioned in case it's related.

Crash analysis

All four reports have the identical faulting path: abort() via std::terminate from an uncaught Objective-C exception thrown during CoreImage Metal context setup:

lastExceptionBacktrace (top frames):
CoreFoundation  __exceptionPreprocess
libobjc.A.dylib objc_exception_throw
CoreFoundation  -[__NSPlaceholderArray initWithObjects:count:]   ← nil element
CoreFoundation  +[NSArray arrayWithObjects:count:]
Metal           __39-[_MTLDevice recordBinaryArchiveUsage:]_block_invoke
libdispatch     _dispatch_client_callout / _dispatch_once_callout
Metal           -[_MTLDevice recordBinaryArchiveUsage:]
Metal           -[_MTLBinaryArchive loadFromURL:error:]
Metal           -[_MTLBinaryArchive initWithOptions:device:url:error:]
CoreImage       +[CIKernelLibrary(Internal) internalBinaryArchiveWithName:device:]
CoreImage       CI::PrecompiledUberFunctions::PrecompiledUberFunctions(CI::MetalContext const*)

Termination: EXC_CRASH (SIGABRT), "Abort trap: 6", ASI: abort() called.

Reading of the stack: when the helper creates a CoreImage MetalContext, CoreImage loads its OS-shipped precompiled shader binary archive (internalBinaryArchiveWithName:). Inside that load, Metal's internal usage-telemetry (recordBinaryArchiveUsage, under dispatch_once) constructs an NSArray with a nil element and throws NSInvalidArgumentException, which nothing in the helper catches → std::terminate → abort.

The archive at fault is Apple's, inside the OS/CoreImage — i.e., this looks like a macOS 27 beta regression that the helper is exposed to, rather than a bug in the helper's own logic. Filing here because (a) the helper could plausibly guard/degrade instead of crash-looping (e.g., catch at the CIContext boundary, fall back to a non-CoreImage path, or surface a clear "OS beta incompatibility" error through the MCP tool result), and (b) you may want a known-issue note for macOS 27 beta users.

Ruled out during diagnosis

  • User Metal shader cache: moved $(getconf DARWIN_USER_CACHE_DIR)/com.apple.metal aside entirely — crash reproduced identically on next launch, with the deeper stack showing the archive comes from CoreImage's internal bundle, not the user cache.
  • CoreSimulator / Xcode setup: xcode-select points at the release Xcode 26.6; simulators are in the default device set; xcrun simctl io <udid> screenshot works perfectly. Everything Apple-side except the beta's Metal telemetry path is healthy.
  • Stale simulators: devices freshly booted; crash occurs regardless of which booted device is targeted.

Repro steps

  1. macOS 27.0 beta 26A5388g, Claude desktop 1.24012.9, any iOS 27 simulator booted.
  2. From a Claude Code session, call the iOS Simulator MCP tool: control { action: "attach", udid: "<booted-udid>" } (or any screenshot action).
  3. Panel opens (attach case), helper aborts within seconds; subsequent calls return "restarting after a crash" then "internal error." Crash report appears in ~/Library/Logs/DiagnosticReports/.

Reproduced 4/4 attempts across ~10 minutes.

Attachments

  • claude-ios-sim-2026-07-31-180417.ips
  • claude-ios-sim-2026-07-31-180446.ips
  • claude-ios-sim-2026-07-31-180919.ips
  • (plus newer claude-ios-sim-*.ips from retry attempts)

All in ~/Library/Logs/DiagnosticReports/. Note: I will attach the .ips files in a follow-up comment (attachments require the web UI).

View original on GitHub ↗

4 Comments

fmgNICE · 1 month ago
lucaslbennett · 29 days ago

Confirming the same crash loop on another machine, with one additional data point that narrows the cause to the helper's sandbox.

Environment

  • Claude desktop app 1.24012.9 (macOS)
  • macOS 27.0 beta, build 26A5388g, Apple Silicon (arm64)
  • Xcode 26.6 (17F113)
  • 23 claude-ios-sim crash reports in ~/Library/Logs/DiagnosticReports/Retired/, oldest 2026-07-25 — so this predates recent app updates

Same signatureSIGABRT, uncaught NSException (nil object in collection):

+[NSArray arrayWithObjects:count:]
__39-[_MTLDevice recordBinaryArchiveUsage:]_block_invoke  (dispatch_once)
-[_MTLBinaryArchive loadFromURL:error:]
+[CIKernelLibrary(Internal) internalBinaryArchiveWithName:device:]
CI::MetalContext::init
+[CIContext contextWithMTLDevice:options:]
FBSimulatorControl (frame capture)

Additional data point — unsandboxed CoreImage+Metal works fine on the same host. This minimal repro succeeds outside the helper:

import CoreImage
import Metal
let dev = MTLCreateSystemDefaultDevice()!
let ctx = CIContext(mtlDevice: dev)
let img = CIImage(color: .red).cropped(to: CGRect(x: 0, y: 0, width: 8, height: 8))
_ = ctx.createCGImage(img, from: img.extent)  // OK, no crash

So the Metal binary-archive load only fails inside the helper's seatbelt profile (Claude.app/Contents/Resources/claude-ios-sim.sb), which has no allowances for Metal shader-cache paths — consistent with the analysis in #80472. Likely the sandbox denial turns a path/archive lookup nil, and Metal's usage-recording puts that nil into an NSArray.

Also matches observed behavior precisely:

  • Input injection (tap, text, swipe) works reliably the whole time — only screenshot/panel streaming (the CoreImage frame-conversion path) dies.
  • From the panel UI this manifests as: click "Attach simulator" → panel briefly attaches → helper aborts on first frame → panel "glitches back" to the attach card. attach MCP calls report success right before the crash.

Possibly-duplicate reports of the same underlying bug: #80177, #80472, #82864, #81520.

lucaslbennett · 29 days ago

Root cause confirmed and a one-rule fix verified locally (same machine as my comment above — macOS 27.0 beta 26A5388g, app 1.24012.9).

Root cause: the seatbelt profile (Resources/claude-ios-sim.sb) allows writes only to CoreSimulator dirs, DARWIN_TMP, and /private/var/tmp. On macOS 27 beta, CoreImage's Metal context creation loads a precompiled binary shader archive, and Metal needs the process's per-bundle shader-cache directory under DARWIN_USER_CACHE_DIR, i.e. /var/folders/…/C/com.anthropic.claude.ios-sim/. The profile denies creating it, the path comes back nil, and -[_MTLDevice recordBinaryArchiveUsage:] throws attempt to insert nil object → SIGABRT.

The bundle identity is the missing trigger: a bare binary running the same CoreImage/Metal code under the same profile passes (no per-bundle cache path), while the identical binary wrapped in an .app with CFBundleIdentifier=com.anthropic.claude.ios-sim crashes with the exact production stack. Repro:

# citest.swift: CIContext(mtlDevice:) + createCGImage — crashes bundled, passes bare
sandbox-exec -f claude-ios-sim.sb -D HELPER_BUNDLE=… -D XCODE_APP=… -D USER_HOME=$HOME \
  -D CORESIM_HOME=… -D CORESIM_LOGS=… -D DARWIN_TMP=… \
  Citest.app/Contents/MacOS/citest
# → NSInvalidArgumentException in -[_MTLDevice recordBinaryArchiveUsage:]_block_invoke

Fix (verified): one added rule, scoped to the helper's own bundle-id cache subtree:

(allow file-write*
  (regex #"^/(private/)?var/folders/[^/]+/[^/]+/C/com\.anthropic\.claude\.ios-sim(/|$)"))

With this appended to the shipped profile, the repro prints OK, Metal creates C/com.anthropic.claude.ios-sim/{com.apple.metal,com.apple.metalfe}, and the real helper stops crash-looping: MCP screenshot returns frames and the live panel attaches and streams. Also corroborates the sandbox analysis in #80472.

A (param …)-based variant would be cleaner than the regex if sidecarSandbox.ts passes DARWIN_USER_CACHE_DIR (sibling C/ of the current DARWIN_TMP param) as a new parameter.

pjacobr · 26 days ago

Reproduced, with two additional data points.

Environment

  • Claude Code CLI: 2.1.205 (MCP iOS Simulator tool, both attach and headless screenshot)
  • macOS: 27.0 beta, build 26A5388g (same build as OP)
  • Xcode: 27.0 beta 4 (27A5228h) selected via xcode-select — so this reproduces with the beta toolchain active as well, ruling out the selected-Xcode version as a factor
  • Hardware: Apple Silicon
  • Simulators: iOS 26.5 runtime device

Isolation

Crash-loops identically with exactly one booted simulator and no other capture processes running (we killed a coexisting serve-sim streamer and shut down a second booted device to eliminate both variables — no change).

Crash stack (from ~/Library/Logs/DiagnosticReports/claude-ios-sim-*.ips)

Same signature as OP, reached via the screenshot path:

claude-ios-sim   SimulatorBridge.screenshot(udid:)
FBSimulatorControl  FBSimulatorImage.image()
FBSimulatorControl  FBSurfaceImageGenerator.image()
CoreImage        -[CIContext initWithOptions:]
CoreImage        CI::MetalContext::init(...)
CoreImage        +[CIKernelLibrary(Internal) internalBinaryArchiveWithName:device:]
Metal            -[_MTLBinaryArchive initWithOptions:device:url:error:]
Metal            -[_MTLBinaryArchive loadFromURL:error:]
Metal            -[_MTLDevice recordBinaryArchiveUsage:]
CoreFoundation   +[NSArray arrayWithObjects:count:]   ← nil object
libobjc          objc_exception_throw
libc++abi        std::__terminate → SIGABRT

Suggested fix / workaround evidence

On this same OS build, the third-party serve-sim streamer works against the same devices: when the hardware H.264 path is unavailable it logs "H.264 was unavailable for this stream, so it fell back to MJPEG" and degrades gracefully. claude-ios-sim takes the CoreImage/Metal path unguarded, so the beta's broken Metal binary-archive load aborts the whole helper on every frame.

Wrapping the CIContext/screenshot render in an exception guard and falling back to a software renderer (kCIContextUseSoftwareRenderer) — or any non-Metal capture path — would keep the panel functional on beta OSes instead of crash-looping.

Showing cached comments. Read the full discussion on GitHub ↗