skills/ios/run-device/SKILL.md
Build, install, and launch an iOS app on a physical iPhone or iPad entirely from the command line (no Xcode GUI), using xcodebuild + devicectl. Use when the user wants to run, test, or screenshot their app on a real device without opening Xcode.
npx skillsauth add rshankras/claude-code-apple-skills run-deviceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Builds, signs, installs, and launches an app on a connected physical device
straight from the terminal — no Xcode Run button. The companion run-simulator
skill covers the Simulator; this is the hardware path, which uses a different
toolchain: devicectl (not simctl), real code signing, and an external tool
for screenshots.
Building proves the code typechecks and signs; launching proves it installs and runs on real hardware. A crash on launch or a failed install is a failure.
Use when the user wants to:
Not for the Simulator (use run-simulator) and not for xcodebuild test.
devicectl pairs once trusted.CODE_SIGN_STYLE = Automatic
and a DEVELOPMENT_TEAM, and the signing Apple ID must already be logged into
Xcode (Settings → Accounts) so -allowProvisioningUpdates can mint profiles.
A free personal team works but apps expire after 7 days.Confirm reachability before building:
xcrun devicectl list devices
The Identifier column is a CoreDevice UUID (e.g. A1B2C3D4-…). It is used by
devicectl for install/launch. It is NOT the value xcodebuild wants — see
the trap in step 2. Pick a row whose State is available (paired) /
connected.
xcrun devicectl list devices # grab the Identifier (CoreDevice UUID) of the paired device
Store it: DEV=<identifier-from-the-Identifier-column>.
Trap: xcodebuild matches on the hardware UDID, not the devicectl
identifier. Passing -destination 'platform=iOS,id=<devicectl-id>' fails with
"Unable to find a device matching the provided destination specifier." Build
for a generic device instead — it produces the same signed .app:
xcodebuild build \
-project MyApp.xcodeproj -scheme MyApp \
-destination 'generic/platform=iOS' \
-allowProvisioningUpdates 2>&1 | tail -5
(Use -workspace MyApp.xcworkspace instead of -project if the project has one.)
Look for ** BUILD SUCCEEDED **. -allowProvisioningUpdates lets xcodebuild
register the device and create/refresh the provisioning profile automatically.
Common failures:
DEVELOPMENT_TEAM=XXXXXXXXXX on
the command line, or fix it in project settings.-allowProvisioningUpdates (above), and
make sure the signing Apple ID is logged into Xcode..app (device build → Debug-iphoneos)Don't guess DerivedData — read it from build settings. Note the directory is
Debug-iphoneos for device builds (vs Debug-iphonesimulator for the Simulator):
eval $(xcodebuild -project MyApp.xcodeproj -scheme MyApp \
-destination 'generic/platform=iOS' -showBuildSettings 2>/dev/null \
| awk -F' = ' '/ TARGET_BUILD_DIR =/{print "DIR=\""$2"\""} / FULL_PRODUCT_NAME =/{print "NAME=\""$2"\""}')
APP="$DIR/$NAME" # .../Debug-iphoneos/MyApp.app
BID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$APP/Info.plist")
xcrun devicectl device install app --device "$DEV" "$APP"
Prints App installed: with the bundleID and installationURL on success.
xcrun devicectl device process launch --device "$DEV" "$BID"
Prints Launched application with <bundle id> ….
Known quirk: adding --terminate-existing (to relaunch a running app)
sometimes returns CoreDeviceError 10004 — "process identifier … could not be determined" even though the app launched. If you see it, run the plain
launch above and verify it's actually running:
xcrun devicectl device info processes --device "$DEV" | grep -i MyApp.app
A matching pid /…/MyApp.app line confirms it's live.
devicectl has no screenshot command, so device screenshots require
libimobiledevice:
brew install libimobiledevice # one-time
idevicescreenshot /tmp/device-shot.png # captures the foreground screen
Then Read /tmp/device-shot.png and verify the change renders.
Caveats:
idevicescreenshot needs the Developer Disk Image mounted. Running the app via
devicectl (steps 4–5) mounts it, so screenshot after launching.idevicescreenshot -u <hardware-udid> …
(idevice_id -l lists hardware UDIDs — again, distinct from the devicectl id).Report:
** BUILD SUCCEEDED ** or the error: lines, then stop)xcodebuild → "Unable to find a device matching the
destination." Build generic/platform=iOS; use the devicectl id only for
install/launch (step 2 trap).-quiet hides success → it suppresses ** BUILD SUCCEEDED **; drop it for
the confirming run or grep error:.10004 on launch → usually a false alarm from --terminate-existing; plain
launch + device info processes confirms (step 5).devicectl works over the network once paired; the device
must be awake and on the same network.xcrun devicectl device uninstall app --device "$DEV" "$BID".development
US web checkout via the StoreKit External Purchase Link entitlement — currently 0% Apple commission (litigation ongoing), how to ship it safely, and how to architect for a commission flip so a future ruling is a config change, not a rewrite. Use when adding external purchase links, weighing web checkout vs IAP, or planning US-storefront pricing strategy.
tools
Revenue beyond the single-app price tag — own-app bundles, Family Sharing as a conversion lever, cross-developer bundles & suites, and institutional licensing via Group Purchases / Apple School & Business Manager. Use when a developer has multiple apps, a subscription worth sharing, complementary indie partners, or school/clinic/business buyers.
testing
Run a structured accessibility audit on an iOS/macOS app — automated XCUITest audits, Accessibility Inspector, manual VoiceOver/Dynamic Type passes, and App Store Accessibility Nutrition Label evaluation. Use before release, when preparing Nutrition Label declarations, or for EU Accessibility Act compliance.
tools
Stage-by-stage audit of an app's App Store growth machinery against a 54-item P0–P9 playbook — every item scored from an App Store Connect MCP call, a codebase check, or an explicit question to the user, then routed to the skill or command that fixes it. Read-only on App Store Connect. Use for a growth audit or scorecard, a pre-launch growth plan, a quarterly re-audit, or "which growth levers am I missing."