iOS Simulator MCP: `control { action: "launch" }` fails with "disclaimer exited with code 143" — sidecar sandbox denies reading the app that `build` produced

Status Open
Maintainer reply None cached
Activity 0 comments · opened Jul 22, 2026

iOS Simulator MCP: control { action: "launch" } fails with "disclaimer exited with code 143" — sidecar sandbox denies reading the app that build produced

Summary

The Claude Code iOS Simulator MCP build action compiles an app successfully, but the launch action then fails every time with:

launch failed: Failed to spawn xcrun (via disclaimer):
  <Claude.app>/Contents/Helpers/disclaimer exited with code 143:

Exit code 143 is 128 + 15 (SIGTERM) — the disclaimer helper is being killed, not erroring on its own. Root cause is an internal inconsistency between two parts of the app: build writes the .app into a directory that the launch sidecar's sandbox profile is not allowed to read.

Environment

  • Claude desktop app, iOS Simulator MCP (build + control tools)
  • macOS on Apple Silicon
  • A booted Simulator device, attached successfully (attach works; screenshot, build, and build_status all work)

Repro

  1. build an app for a booted simulator → succeeds, returns the built .app path under

~/Library/Application Support/Claude/simulator-builds/<hash>/DerivedData/Build/Products/Debug-iphonesimulator/<App>.app.

  1. control { action: "launch", app_path: "<that path>" }.
  2. After a delay (~40s, i.e. a watchdog timeout), it fails with the 143 error above. Reproduces every time.

Meanwhile, installing/launching the same built .app with plain xcrun simctl install + xcrun simctl launch from an unsandboxed shell works instantly. So the app and the simulator are fine — only the sandboxed launch path fails.

Root cause

launch runs simctl install inside the sandboxed sidecar ([Simulator] sidecar sandbox=enabled profile=…/Resources/claude-ios-sim.sb). That seatbelt profile is deny-by-default with a broad (allow file-read*), and then denies file-read-data under the entire home directory, re-allowing content reads for only these subpaths:

  • HELPER_BUNDLE
  • CORESIM_HOME
  • CORESIM_LOGS
  • XCODE_APP

The build action's output directory (~/Library/Application Support/Claude/simulator-builds/…) is under $HOME and is not in that allow-list. So inside the sidecar:

  • stat() on the built app succeeds (metadata reads aren't denied), so the helper "sees" the app, but
  • reading the app's bytes is denied, so simctl install cannot copy it into the device container.

Per the profile's own header comment, a missing read rule doesn't fail loudly — CoreSimulator turns the denied read into a hang. The sidecar's watchdog then times out and SIGTERMs the process tree, so disclaimer (the TCC-disclaim wrapper around xcrun) exits 143.

Proof

Running sandbox-exec with the shipped claude-ios-sim.sb profile and the same -D parameters the sidecar uses:

| Operation | Target | Result |
| --- | --- | --- |
| read bytes (head -c 16) | built app binary under …/Claude/simulator-builds/… | Operation not permitted |
| read bytes (head -c 16) | a file under CORESIM_HOME | allowed |
| stat (metadata) | same built app binary | allowed |

And the decisive end-to-end confirmation: copying the built .app into a read-allowed path (~/Library/Developer/CoreSimulator/…) and pointing launch there makes the native launch action succeed. Nothing else changed.

Workaround (for anyone hitting this now)

Either:

  • Stage the built app into a read-allowed path before launching:

cp -R "<built>/<App>.app" ~/Library/Developer/CoreSimulator/<somedir>/ and launch from there; or

  • Skip the launch action and use xcrun simctl install <udid> <app> + xcrun simctl launch <udid> <bundle-id> from a normal (unsandboxed) shell.

Suggested fix

Make build's output location and the launch sandbox's read allow-list agree. Either:

  1. Add the simulator-builds output directory (or the whole ~/Library/Application Support/Claude/simulator-builds tree) as a file-read-data carve-out in claude-ios-sim.sb; or
  2. Have build emit the .app into an already-allowed location (e.g. under CORESIM_HOME) so launch can read it without a profile change.

Option 1 is the smaller, more targeted change. Because a missing rule surfaces as an opaque hang→SIGTERM rather than a clear "permission denied", a regression test that builds and then launches through the sandbox (not just an unsandboxed simctl) would catch future drift.

View original on GitHub ↗