SCRIPTS="${CLAUDE_PLUGIN_ROOT}/skills/aio-ios-device-debug/scripts"
iOS Device Debug
Environment
- xcode-select: !
xcode-select -p 2>/dev/null || echo "NOT INSTALLED" - idevicesyslog: !
which idevicesyslog 2>/dev/null || echo "NOT INSTALLED — brew install libimobiledevice" - idevicecrashreport: !
which idevicecrashreport 2>/dev/null || echo "NOT INSTALLED — brew install libimobiledevice" - python3: !
which python3 2>/dev/null || echo "NOT INSTALLED" - pymobiledevice3: !
python3 -c "import pymobiledevice3" 2>/dev/null && echo "installed" || echo "NOT INSTALLED — pip3 install pymobiledevice3" - Connected devices: !
idevice_id -l 2>/dev/null || echo "none detected (or idevice_id missing)"
Debug iOS apps on physical devices: build, install, launch, capture logs, pull crash reports, and analyze crashes — all from the terminal without Xcode GUI.
Prerequisites
Required tools (check before starting):
- Xcode with command line tools (
xcode-select --install) - libimobiledevice:
brew install libimobiledevice(providesidevicesyslog,idevicecrashreport) - pymobiledevice3:
pip3 install pymobiledevice3(required for screenshots on iOS 17+) - Python 3: for crash report parsing and pymobiledevice3
- Physical iOS device connected via USB or WiFi
Workflow Overview
The debug workflow follows this sequence:
- Discover device — find device ID
- Build & install — compile and deploy to device
- Launch & capture logs — run app while recording syslog
- Check for crash — search logs for Corpse/signal
- Pull crash reports — extract .ips files from device
- Analyze crash — parse .ips for stack trace and root cause
Scripts
| Script | Purpose |
|---|---|
$SCRIPTS/device-list.sh |
List connected devices with both devicectl and xcodebuild IDs |
$SCRIPTS/build-install.sh |
Build scheme and install on device |
$SCRIPTS/launch-and-log.sh |
Launch app, capture syslog, check for crashes |
$SCRIPTS/pull-crash-reports.sh |
Pull .ips crash reports from device |
$SCRIPTS/analyze-crash.sh |
Parse .ips file into human-readable analysis |
$SCRIPTS/screenshot.sh |
Capture screenshot from device (iOS 17+ compatible) |
Step-by-Step Debug Process
1. Discover Device
Two ID systems exist for iOS devices — xcodebuild and devicectl use different IDs:
# Get xcodebuild device ID (needed for build/install)
xcodebuild -scheme <Scheme> -showdestinations 2>/dev/null | grep "platform:iOS," | grep -v Simulator
# Get devicectl UUID (needed for devicectl commands)
xcrun devicectl list devices
The xcodebuild ID (e.g., 00008140-...) is used for building and installing. The devicectl UUID is different. Always verify which ID a command expects.
Capture Screenshot
On iOS 17+, idevicescreenshot and the deprecated pymobiledevice3 developer screenshot API both fail. Use the DVT screenshot method via pymobiledevice3 tunnel instead.
Prerequisites:
- Install pymobiledevice3:
pip3 install pymobiledevice3 - Start tunneld (requires sudo, run once in background):
sudo python3 -m pymobiledevice3 remote tunneld -p tcp - Developer image must be mounted (usually already done if Xcode has connected to the device):
python3 -m pymobiledevice3 mounter auto-mount
Capture screenshot:
# Using the script (recommended)
bash $SCRIPTS/screenshot.sh /tmp/device_screenshot.png
# Manual command
python3 -m pymobiledevice3 developer dvt screenshot --tunnel "" /tmp/device_screenshot.png
The --tunnel "" flag auto-discovers the device through the running tunneld service.
What does NOT work on iOS 17+:
| Method | Status | Error |
|---|---|---|
idevicescreenshot |
FAILS | "Could not start screenshotr service: Invalid service" |
pymobiledevice3 developer screenshot |
FAILS | "InvalidServiceError" (deprecated API) |
pymobiledevice3 developer screenshot --tunnel "" |
FAILS | deprecated API, same error through tunnel |
xcrun devicectl |
N/A | No screenshot subcommand exists |
pymobiledevice3 developer dvt screenshot --tunnel "" |
WORKS | DVT API through tunneld |
2. Build & Install
# Build for device
xcodebuild -scheme <Scheme> -destination 'platform=iOS,id=<DEVICE_ID>' -allowProvisioningUpdates build
# Find the .app
find ~/Library/Developer/Xcode/DerivedData -path "*/<Scheme>-*/Build/Products/Debug-iphoneos/<Scheme>.app" -maxdepth 5
# Install
xcrun devicectl device install app --device <DEVICECTL_UUID> /path/to/App.app
Or use the combined script:
bash $SCRIPTS/build-install.sh <Scheme> <XCODEBUILD_DEVICE_ID>
3. Launch & Capture Logs
Start syslog capture BEFORE launching the app to catch early crashes:
# Start syslog in background, writing to file
idevicesyslog --no-colors > /tmp/app_syslog.txt 2>&1 &
SYSLOG_PID=$!
sleep 3 # Wait for connection
# Launch app
xcrun devicectl device process launch --device <DEVICECTL_UUID> <BUNDLE_ID>
# Wait for crash or behavior
sleep 20
# Stop syslog
kill $SYSLOG_PID
Or use the combined script:
bash $SCRIPTS/launch-and-log.sh <BUNDLE_ID> <DEVICECTL_UUID> 20
4. Analyze Logs
After capturing, filter for relevant information:
# Find app PID
grep -oE 'AppName\[[0-9]+\]' /tmp/app_syslog.txt | sort -u
# Check for crash (Corpse = kernel generating crash report)
grep "Corpse" /tmp/app_syslog.txt
# Find crash signal
grep -E "Process exited.*signal" /tmp/app_syslog.txt
# Example output: code:SIGTRAP(5) = Swift runtime trap
# Example output: code:SIGABRT(6) = abort/exception
# Filter app-only logs
grep "AppName\[PID\]" /tmp/app_syslog.txt | grep -v "CoreFoundation\|BaseBoard"
5. Pull & Analyze Crash Reports
# Pull all crash reports from device
mkdir -p /tmp/crashes
idevicecrashreport -e /tmp/crashes/
# Find app-specific crashes
ls /tmp/crashes/ | grep -i AppName
# Analyze the latest crash
bash $SCRIPTS/analyze-crash.sh /tmp/crashes/AppName-2026-02-09-173322.ips
The analyze script outputs: exception type, signal, faulting thread, stack trace with app frames highlighted, and a diagnosis based on the signal type.
Key Signals and What They Mean
| Signal | Exception | Meaning |
|---|---|---|
| SIGTRAP | EXC_BREAKPOINT | Swift runtime trap: force unwrap nil, precondition, actor isolation violation |
| SIGABRT | EXC_CRASH | Abort: uncaught exception, fatalError(), assertion |
| SIGSEGV | EXC_BAD_ACCESS | Bad memory: use-after-free, null pointer, dangling pointer |
| SIGKILL | — | System kill: watchdog timeout, OOM (jetsam), background time exceeded |
Common Pitfalls
- Wrong device ID:
xcodebuildanddevicectluse DIFFERENT IDs. Do not mix them. - Screenshot on iOS 17+:
idevicescreenshotand the deprecated pymobiledevice3 screenshot API do NOT work. You MUST usepymobiledevice3 developer dvt screenshot --tunnel "". Requires tunneld running withsudo python3 -m pymobiledevice3 remote tunneld -p tcp. - Syslog disconnects:
idevicesyslogcan disconnect. Always verify logs were captured (check file size). - Benign CoreMotion warning:
Error reading com.apple.CoreMotion.plistis a system log, not an app error. - Swift 6 actor isolation:
dispatch_assert_queue_failin stack = closure running on wrong queue for@MainActorclass. Fix with@Sendableannotation. - Process filter:
idevicesyslog -p <bundle_id>can be too aggressive. Prefer capturing all logs to a file and filtering with grep afterward.
Additional Resources
Reference Files
references/crash-patterns.md— Detailed crash signal patterns, Swift 6 concurrency crashes, CoreMotion/CoreLocation issues, device ID systems, log filtering recipes