iOS Simulator MCP: `control { action: "launch" }` fails with "disclaimer exited with code 143" — sidecar sandbox denies reading the app that `build` produced
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+controltools) - macOS on Apple Silicon
- A booted Simulator device, attached successfully (
attachworks;screenshot,build, andbuild_statusall work)
Repro
buildan app for a booted simulator → succeeds, returns the built.apppath under
~/Library/Application Support/Claude/simulator-builds/<hash>/DerivedData/Build/Products/Debug-iphonesimulator/<App>.app.
control { action: "launch", app_path: "<that path>" }.- 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_BUNDLECORESIM_HOMECORESIM_LOGSXCODE_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 installcannot 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
launchaction and usexcrun 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:
- Add the
simulator-buildsoutput directory (or the whole~/Library/Application Support/Claude/simulator-buildstree) as afile-read-datacarve-out inclaude-ios-sim.sb; or - Have
buildemit the.appinto an already-allowed location (e.g. underCORESIM_HOME) solaunchcan 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.