
Axiom Tools
- 696 installs
- 1.1k repo stars
- Updated August 3, 2026
- charleswiltgen/axiom
axiom-tools is an agent skill that routes iOS and Swift work through Axiom’s xclog, xcsym, xcui, and xcprof utilities for console capture, crash symbolication, simulator UI validation, and trace profiling.
About
axiom-tools is a Claude Code skill from charleswiltgen/axiom that onboards developers to the Axiom iOS agent toolkit and its four bundled CLIs. xclog captures Xcode console output, xcsym symbolicates .ips, MetricKit, and .crash reports, xcui drives and validates simulator UI and accessibility, and xcprof analyzes xctrace and CPU profiles. Developers reach for axiom-tools when debugging Swift apps in the simulator, triaging symbolicated crashes, validating accessibility trees, or profiling hot paths without manually chaining separate Xcode workflows.
- Routing table maps questions to getting-started, xclog, xcsym, xcui, and xcprof reference docs
- xclog captures console output from iOS runs
- xcsym symbolicates .ips, MetricKit, and .crash reports
- xcui drives and validates simulator UI and accessibility scripts
- xcprof analyzes xctrace CPU and network profiles
Axiom Tools by the numbers
- 696 all-time installs (skills.sh)
- Ranked #263 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-toolsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 696 |
|---|---|
| repo stars | ★ 1.1k |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 3, 2026 |
| Repository | charleswiltgen/axiom ↗ |
How do you debug iOS simulator crashes and profiles?
Route iOS and Swift agent work through Axiom’s xclog, xcsym, xcui, and xcprof tools for console capture, crash symbolication, simulator UI checks, and trace profiling.
Who is it for?
iOS and Swift developers using Axiom who need guided routing across xclog, xcsym, xcui, and xcprof during simulator debugging.
Skip if: Android or cross-platform teams without Xcode simulators should skip axiom-tools.
When should I use this skill?
A developer asks about Axiom onboarding, xclog console capture, xcsym crash symbolication, xcui simulator checks, or xcprof trace analysis.
What you get
Console logs, symbolicated crash reports, simulator UI validation results, and xctrace CPU profile analysis
- Console capture output
- Symbolicated crash report
- Simulator UI validation
By the numbers
- Documents 4 Axiom CLI tools: xclog, xcsym, xcui, and xcprof
Files
Axiom Tools & Onboarding
This suite covers Axiom itself — how to use it, what's available, and the tools that ship with it.
Routing
| Question | Read |
|---|---|
| "How do I use Axiom?" / "What skills are available?" | skills/getting-started.md |
| "How do I capture console output?" / "What is xclog?" | skills/xclog-ref.md |
| "How do I symbolicate a crash?" / "What is xcsym?" / "Why is my crash unsymbolicated?" | skills/xcsym-ref.md |
| "How do I drive/validate the sim UI?" / "What is xcui?" / "How do I script accessibility checks?" | skills/xcui-ref.md |
| "How do I analyze a trace / CPU or network profile?" / "What is xcprof?" / "Why does the profiler report no findings?" | skills/xcprof-ref.md |
Using Axiom Skills
The content below is the core discipline for Axiom's routing system — it establishes the rule that Axiom skills must be checked before any iOS/Swift response.
<EXTREMELY-IMPORTANT> If you think there is even a 1% chance an Axiom skill might apply to your iOS/Swift task, you ABSOLUTELY MUST check for the skill.
IF AN AXIOM SKILL APPLIES TO YOUR iOS/SWIFT TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
This is not negotiable. This is not optional. You cannot rationalize your way out of this. </EXTREMELY-IMPORTANT>
The Rule
Check for Axiom skills BEFORE ANY RESPONSE when working with iOS/Swift projects. This includes clarifying questions. Even 1% chance means check first.
Red Flags — iOS-Specific Rationalizations
These thoughts mean STOP—you're rationalizing:
| Thought | Reality |
|---|---|
| "This is just a simple build issue" | Build failures have patterns. Check axiom-build first. |
| "I can fix this SwiftUI bug quickly" | SwiftUI issues have hidden gotchas. Check axiom-swiftui first. |
| "Let me just add this database column" | Schema changes risk data loss. Check axiom-data first. |
| "This async code looks straightforward" | Swift concurrency has subtle rules. Check axiom-concurrency first. |
| "I'll debug the memory leak manually" | Leak patterns are documented. Check axiom-performance first. |
| "Let me explore the Xcode project first" | Axiom skills tell you HOW to explore. Check first. |
| "I remember how to do this from last time" | iOS changes constantly. Skills are up-to-date. |
| "This iOS/platform version doesn't exist" | If it postdates your training, you can't know that. Apple shipped iOS 26 at WWDC 2025 (18 → 26, 19-25 skipped). Invoke Axiom skills for post-cutoff facts. |
| "The user just wants a quick answer" | Quick answers without patterns create tech debt. Check skills first. |
| "This doesn't need a formal workflow" | If an Axiom skill exists for it, use it. |
| "I'll gather info first, then check skills" | Skills tell you WHAT info to gather. Check first. |
Skill Priority for iOS Development
When multiple Axiom skills could apply, use this priority:
1. Environment/Build first (axiom-build) — Fix the environment before debugging code 2. Architecture patterns (axiom-swiftui, axiom-data, axiom-concurrency) — These determine HOW to structure the solution 3. Implementation details (axiom-integration, axiom-ai, axiom-vision) — These guide specific feature work
Examples:
- "Xcode build failed" → axiom-build first (environment)
- "Add SwiftUI screen" → axiom-swiftui first (architecture), then maybe axiom-integration if using system features
- "App is slow" → axiom-performance first (diagnose), then fix the specific domain
- "Network request failing" → axiom-build first (environment check), then axiom-networking (implementation)
iOS Project Detection
Axiom skills apply when:
- Working directory contains
.xcodeprojor.xcworkspace - User mentions iOS, Swift, Xcode, SwiftUI, UIKit
- User asks about Apple frameworks (SwiftData, CloudKit, etc.)
- User reports iOS-specific errors (concurrency, memory, build failures)
Using Axiom Router Skills
Axiom uses router skills for progressive disclosure:
1. Check the appropriate router skill first (axiom-build, axiom-swiftui, axiom-data, etc.) 2. Router will invoke the specialized skill(s) you actually need 3. Follow the specialized skill exactly
Do not skip the router. Routers have decision logic to select the right specialized skill.
Multi-Domain Questions
When a question spans multiple domains, invoke ALL relevant routers — don't stop after the first one.
Examples:
- "My SwiftUI view doesn't update when SwiftData changes" → invoke both axiom-swiftui AND axiom-data
- "My widget isn't showing updated data from SwiftData" → invoke both axiom-integration AND axiom-data
- "My Foundation Models session freezes the UI" → invoke both axiom-ai AND axiom-concurrency
- "My Core Data saves lose data from background tasks" → invoke both axiom-data AND axiom-concurrency
How to tell: If the question mentions symptoms from two different domains, or involves two different frameworks, invoke both routers. Each router has cross-domain routing guidance for common overlaps.
Backward Compatibility
- Direct skill invocation still works:
/skill axiom-concurrency - Commands work unchanged:
/axiom:fix-build,/axiom:audit-accessibility - Agents work via routing or direct command invocation
When Axiom Skills Don't Apply
Skip Axiom skills for:
- Non-iOS/Swift projects (Android, web, backend)
- Generic programming questions unrelated to Apple platforms
- Questions about Claude Code itself (use claude-code-guide skill)
But when in doubt for iOS/Swift work: check first, decide later.
Resources
Skills: axiom-swiftui, axiom-concurrency, axiom-data, axiom-build, axiom-performance
Axiom tools: xclog (simulator console capture, skills/xclog-ref.md), xcsym (crash symbolication for .ips, MetricKit, legacy .crash text files, and Xcode Organizer .xccrashpoint bundles, skills/xcsym-ref.md), xcui (scriptable sim UI & accessibility testing, skills/xcui-ref.md), xcprof (structured xctrace CPU & network profile analysis, skills/xcprof-ref.md)
Getting Started with Axiom
Welcome! This skill helps new users discover the most relevant Axiom skills for their situation.
How This Skill Works
1. Ask the user 2-3 targeted questions about their project 2. Provide personalized skill recommendations (3-5 skills max) 3. Show example prompts they can try immediately 4. Include a complete skill reference for browsing
Step 1: Ask Questions
Use the AskUserQuestion tool to gather context:
Question 1: Current Focus
Question: "What brings you to Axiom today?"
Header: "Focus"
Options:
- "Debugging an issue" → Prioritize diagnostic skills
- "Optimizing performance" → Prioritize profiling skills
- "Adding new features" → Prioritize reference skills
- "Code review / quality check" → Prioritize audit commands
- "Just exploring" → Show overviewQuestion 2: Tech Stack
Question: "What's your primary tech stack?"
Header: "Stack"
Options:
- "SwiftUI (iOS 16+)" → SwiftUI-focused skills
- "UIKit" → UIKit-focused skills
- "Mixed SwiftUI + UIKit" → Both
- "Starting new project" → Best practices skillsQuestion 3: Pain Points (Optional, Multi-Select)
Only ask if "Debugging an issue" was selected:
Question: "Which areas are you struggling with?"
Header: "Pain Points"
Multi-select: true
Options:
- "Xcode/build issues"
- "Memory leaks"
- "UI/animation problems"
- "Database/persistence"
- "Networking"
- "Concurrency/async"
- "Accessibility"Step 2: Provide Personalized Recommendations
Based on answers, recommend 3-5 skills using this matrix:
If "Debugging an issue"
Always recommend: axiom:xcode-debugging (universal starting point)
Then add based on pain points:
- Xcode/build → xcode-debugging, axiom-build (skills/build-debugging.md)
- Memory leaks → memory-debugging, axiom-performance (skills/objc-block-retain-cycles.md)
- UI/animation (SwiftUI) → axiom-swiftui (debugging, performance references)
- UI/animation (UIKit) → uikit-animation-debugging, axiom-uikit (skills/auto-layout-debugging.md)
- Database → database-migration, axiom-data (skills/sqlitedata-migration.md) (decision guide)
- Networking → axiom-networking
- Concurrency → swift-concurrency
- Accessibility → accessibility-diag
If "Optimizing performance"
SwiftUI stack: 1. performance-profiling (decision trees for tools) 2. axiom-swiftui (performance reference — SwiftUI Instrument) 3. axiom-swiftui (debugging reference — view update issues)
UIKit/Mixed: 1. performance-profiling (Instruments guide) 2. memory-debugging (leak detection) 3. uikit-animation-debugging (CAAnimation issues)
If "Adding new features"
Design decisions:
- hig (quick design decisions, checklists)
- hig-ref (comprehensive HIG reference)
iOS 26+ features:
- liquid-glass (material design system)
- foundation-models (on-device AI)
- axiom-swiftui (iOS 26 reference — complete iOS 26 guide)
Navigation patterns:
- axiom-swiftui (navigation reference — iOS 18+ Tab/Sidebar, deep linking)
Integrations:
- app-intents-ref (Siri, Shortcuts, Spotlight)
- networking (Network.framework modern patterns)
Data persistence:
- Ask: "Which persistence framework?" → swiftdata, axiom-data (skills/sqlitedata.md), or grdb
- Migration: axiom-data (skills/sqlitedata-migration.md), axiom-data (skills/realm-migration-ref.md)
If "Code review / quality check"
Start with audit commands (quick wins): 1. /axiom:audit accessibility — WCAG compliance 2. /axiom:audit concurrency — Swift 6 violations 3. /axiom:audit memory — Leak patterns 4. /axiom:audit core-data — Migration safety 5. /axiom:audit networking — Deprecated APIs
Then suggest:
- Review skills based on what audits find
If "Just exploring"
Show the complete skill index (see below) and explain categories.
Step 3: Output Format
After gathering answers, output:
## Your Recommended Skills
Based on your answers, here are the skills most relevant to you right now:
### [Icon] [Category Name]
**axiom:[skill-name]** — [One-line description]
> Try: "[Example prompt they can use immediately]"
[Repeat for 3-5 skills]
### Quick Wins
Run these audit commands to find issues automatically:
- `/axiom:audit [name]` — [What it finds]
## What's Next
1. **Try the example prompts above** — Copy/paste to see how skills work
2. **Run an audit command** — Get immediate actionable insights
3. **Describe your problem** — I'll suggest the right skill
4. **Browse the complete index below** — Explore the full skill catalog
---
[Include the Complete Skill Reference below]Complete Skill Reference
Include this reference section in every response for browsing:
Debugging & Troubleshooting
Environment & Build Issues
- xcode-debugging — BUILD FAILED, simulator hangs, zombie processes, environment-first diagnostics
- build-debugging — Dependency conflicts, CocoaPods/SPM failures, Multiple commands produce
Memory & Performance
- memory-debugging — Memory growth, retain cycles, leak diagnosis with Instruments
- performance-profiling — Decision trees for Instruments (Time Profiler, Allocations, Core Data, Energy)
- objc-block-retain-cycles — Objective-C block memory leaks, weak-strong pattern
UI Debugging
- axiom-swiftui (debugging reference) — View update issues, struct mutation, binding identity, view recreation
- axiom-swiftui (performance reference) — SwiftUI Instrument (iOS 26), long view bodies, Cause & Effect Graph
- uikit-animation-debugging — CAAnimation completion, spring physics, gesture+animation jank
- auto-layout-debugging — Auto Layout conflicts, constraint debugging (not yet in manifest)
Concurrency & Async
- swift-concurrency — Swift 6 strict concurrency, @concurrent, actor isolation, Sendable, data races
UI & Design (iOS 26+)
Liquid Glass (Material Design)
- liquid-glass — Implementation, Regular vs Clear variants, design review defense
- liquid-glass-ref — Complete app-wide adoption guide (icons, controls, navigation, windows)
Layout & Navigation
- axiom-swiftui (layout reference) — ViewThatFits vs AnyLayout vs onGeometryChange, decision trees, iOS 26 free-form windows
- axiom-swiftui (navigation reference) — NavigationStack vs NavigationSplitView, deep links, coordinator patterns, iOS 18+ Tab/Sidebar
- axiom-swiftui (navigation diagnostics) — Navigation not responding, unexpected pops, deep link failures, state loss
Testing
- ui-testing — Recording UI Automation (Xcode 26), condition-based waiting, accessibility-first patterns
Persistence
Frameworks
- swiftdata — @Model, @Query, @Relationship, CloudKit, iOS 26 features, Swift 6 concurrency
- sqlitedata — Point-Free SQLiteData, @Table, FTS5, CTEs, JSON aggregation, CloudKit sync
- grdb — Raw SQL, complex joins, ValueObservation, DatabaseMigrator, performance
- database-migration — Safe schema evolution for SQLite/GRDB, additive migrations, prevents data loss
Migration Guides
- sqlitedata-migration — Decision guide, pattern equivalents, performance benchmarks
- realm-migration-ref — Realm → SwiftData migration (Realm Device Sync sunset Sept 2025)
Networking
- networking — Network.framework (iOS 12-26), NetworkConnection (iOS 26), structured concurrency
- networking-diag — Connection timeouts, TLS failures, data not arriving, performance issues
- network-framework-ref — Complete API reference, TLV framing, Coder protocol, Wi-Fi Aware
Apple Intelligence (iOS 26+)
- foundation-models — On-device AI, LanguageModelSession, @Generable, streaming, tool calling
- foundation-models-diag — Context exceeded, guardrails, slow generation, availability issues
- foundation-models-ref — Complete API reference, all 26 WWDC examples
Design & UI Guidelines
- hig — Quick design decisions, color/background/typography choices, HIG compliance checklists
- hig-ref — Comprehensive Human Interface Guidelines reference with code examples
Integrations
- app-intents-ref — Siri, Apple Intelligence, Shortcuts, Spotlight (iOS 16+)
- axiom-swiftui (iOS 26 reference) — iOS 26 SwiftUI features, @Animatable, 3D layout, WebView, AttributedString
- avfoundation-ref — Audio APIs, bit-perfect DAC, iOS 26 spatial audio, ASAF/APAC
Diagnostics (Systematic Troubleshooting)
- accessibility-diag — VoiceOver, Dynamic Type, color contrast, WCAG compliance, App Store defense
- core-data-diag — Schema migration crashes, thread-confinement, N+1 queries
Audit Commands (Quick Scans)
/axiom:audit accessibility— VoiceOver labels, Dynamic Type, contrast, touch targets/axiom:audit concurrency— Swift 6 violations, unsafe tasks, missing @MainActor/axiom:audit memory— Timer leaks, observer leaks, closure captures, delegate cycles/axiom:audit core-data— Migration risks, thread violations, N+1 queries/axiom:audit networking— Deprecated APIs (SCNetworkReachability, CFSocket), anti-patterns/axiom:audit liquid-glass— Glass adoption opportunities, toolbar improvements, blur migration
Skill Categories Explained
- Discipline skills (no suffix) — Step-by-step workflows with pressure scenarios, TDD-tested
- Diagnostic skills (-diag suffix) — Systematic troubleshooting with production crisis defense
- Reference skills (-ref suffix) — Comprehensive API guides with WWDC examples
Quick Decision Trees
"My build is failing" → Start: axiom:xcode-debugging → If dependency issue: axiom:build-debugging
"App is slow" → Start: axiom:performance-profiling (decision trees) → If SwiftUI: axiom-swiftui (performance reference) → If memory grows: axiom:memory-debugging
"Memory leak" → Start: axiom:memory-debugging → If Objective-C blocks: axiom:objc-block-retain-cycles
"SwiftUI view issues" → Start: axiom-swiftui (debugging reference) → If performance: axiom-swiftui (performance reference)
"Navigation problems" → Start: axiom-swiftui (navigation diagnostics) → For patterns: axiom-swiftui (navigation reference)
"Which database?" → Decision guide: axiom:sqlitedata-migration → Then: axiom:swiftdata, axiom:sqlitedata, or axiom:grdb
"iOS 26 design" → Start: axiom:liquid-glass → Complete guide: axiom:liquid-glass-ref
"Code quality check" → Run: /axiom:audit accessibility, /axiom:audit concurrency, /axiom:audit memory → Fix issues with relevant skills
How Skills Work
Axiom skills load automatically — you don't need to memorize names or commands.
Automatic triggering (most common): Just describe your problem naturally. Claude detects which skill applies and loads it.
- "My SwiftData CloudKit sync isn't working" → loads
cloud-sync-diag - "I'm getting Sendable errors in Swift 6" → loads
swift-concurrency
Explicit invocation: If you know the skill name, invoke it directly:
/skill axiom-concurrency- See axiom-design (skills/liquid-glass.md)
Audit commands: Run automated scans with slash commands:
/axiom:audit memory— scans for memory leak patterns/axiom:audit concurrency— scans for Swift 6 violations
Key insight: You don't need to know skill names. Describe what you're working on and Axiom routes to the right skill automatically.
Tips
- Describe your problem — Claude will suggest the right skill
- Run audits first — Quick wins with automated scans
- Start with diagnostic skills — When troubleshooting specific issues
- Use reference skills — When implementing new features
- All skills are searchable — Just describe what you need
xclog Reference (iOS Simulator Console Capture)
xclog captures iOS simulator console output by combining simctl launch --console (print/debugPrint/NSLog) with log stream --style json (os_log/Logger). Single binary, no dependencies.
Invocation
xclog is on PATH as a bare command (Claude Code 2.1.91+ resolves plugin bin/ entries automatically). Just run xclog <subcommand> — no prefix, no path lookup.
When to Use
- Runtime crashes — capture what the app logged before crashing
- Silent failures — network calls, data operations that fail without UI feedback
- Debugging print() output — see what the app is printing to stdout/stderr
- os_log analysis — structured logging with subsystem, category, and level filtering
- Automated log capture —
--timeoutand--max-linesfor bounded collection
Critical Best Practices
Check `.axiom/preferences.yaml` first. If no saved preferences, run list before launch to discover the correct bundle ID.
App already running? launch will terminate it and relaunch. Use attach if you need to preserve current state (os_log only — no print() capture).
# 1. FIRST: Check .axiom/preferences.yaml for saved device and bundle ID
# 2. If no preferences: Discover installed apps
xclog list
# 3. Find the target app's bundle_id from output
# 4. THEN: Launch with the correct bundle ID (restarts app)
xclog launch com.example.MyApp --timeout 30s --max-lines 200
# OR: Attach to running app without restarting (os_log only)
xclog attach MyApp --timeout 30s --max-lines 200Preferences
Axiom saves simulator preferences to .axiom/preferences.yaml in the project root. Check this file before running `xclog list` — if preferences exist, use the saved device and bundle ID directly.
Reading Preferences
Before running xclog list, read .axiom/preferences.yaml:
simulator:
device: iPhone 16 Pro
deviceUDID: 1A2B3C4D-5E6F-7890-ABCD-EF1234567890
bundleId: com.example.MyAppIf the file exists and contains a simulator section, use the saved deviceUDID and bundleId for xclog commands. Skip xclog list unless the user asks for a different app or the saved values fail.
xclog launch <bundleId> --device <deviceUDID> --timeout 30s --max-lines 200If the file doesn't exist or the simulator section is missing, fall back to xclog list discovery.
If the saved deviceUDID is not found among available simulators (xclog or simctl fails), fall back to discovery and save the new selection.
If the YAML is malformed, warn the developer and fall back to discovery. Do not overwrite a malformed file.
Writing Preferences
After a successful xclog launch or when the user selects a target app from xclog list output, save the device and bundle ID:
1. If .axiom/ doesn't exist, create it. Then check .gitignore: if the file exists, check if any line matches .axiom/ exactly — if not, append .axiom/ on a new line. If .gitignore doesn't exist, create it with .axiom/ as its content. 2. Read .axiom/preferences.yaml if it exists (to preserve other keys) 3. Update the simulator: section with device, deviceUDID, and bundleId 4. Write the merged YAML back using the Write tool
Write the same simulator: structure shown in Reading Preferences above.
Commands
list — Discover Installed Apps
xclog list
xclog list --device <udid>Output (JSON lines):
{"bundle_id":"com.example.MyApp","name":"MyApp","version":"1.2.0"}
{"bundle_id":"com.apple.mobilesafari","name":"Safari","version":"18.0"}launch — Full Console Capture
Launches the app and captures ALL output: print(), debugPrint(), NSLog(), os_log(), Logger.
# Basic launch (JSON output, runs until app exits or Ctrl-C)
xclog launch com.example.MyApp
# Bounded capture (recommended for LLM use)
xclog launch com.example.MyApp --timeout 30s --max-lines 200
# Filter by subsystem
xclog launch com.example.MyApp --subsystem com.example.MyApp.networking
# Filter by regex
xclog launch com.example.MyApp --filter "error|warning|crash"
# Save to file
xclog launch com.example.MyApp --output /tmp/console.log --timeout 60sattach — Monitor Running Process
Attaches to a running process via os_log only. Does NOT capture print()/debugPrint(). Simulator only.
# By process name
xclog attach MyApp --timeout 30s
# By PID
xclog attach 12345 --max-lines 100
# Filter for errors only
xclog attach MyApp --filter "(?i)error|fault"show — Historical Log Search (Simulator + Physical Device)
Searches recent logs without needing proactive capture. Works with both simulator and connected physical devices.
# Simulator: show last 5 minutes of MyApp logs
xclog show MyApp --last 5m --max-lines 200
# Simulator: show last 10 minutes, errors only
xclog show MyApp --last 10m --max-lines 100 --filter "(?i)error|fault"
# Physical device: collect and show logs (device must be connected + unlocked)
xclog show MyApp --device-udid 00008101-... --last 5m --max-lines 200
# By PID
xclog show 12345 --last 2mPhysical device workflow: show --device-udid runs log collect to pull a log archive from the device over USB, then parses it locally. The device must be connected and unlocked.
When to use `show` vs `attach`:
show— "What just happened?" (post-mortem, no setup needed)attach— "What's happening now?" (live streaming, must be running before the event)
Output Format
Default output is JSON lines (one JSON object per line).
JSON Schema (Default)
{
"time": "10:30:45.123",
"source": "os_log",
"level": "error",
"subsystem": "com.example.MyApp",
"category": "networking",
"process": "MyApp",
"pid": 12345,
"text": "Connection failed: timeout"
}| Field | Type | Present | Description |
|---|---|---|---|
| time | string | Always | HH:MM:SS.mmm timestamp |
| source | string | Always | "print", "stderr", or "os_log" |
| level | string | os_log only | "debug", "default", "info", "error", "fault" |
| subsystem | string | os_log only | Reverse-DNS subsystem (e.g. com.example.MyApp) |
| category | string | os_log only | Log category within subsystem |
| process | string | os_log only | Process binary name |
| pid | int | os_log only | Process ID |
| text | string | Always | The log message content |
Fields not applicable to a source are omitted (not null).
Human-Readable Mode
xclog attach MyApp --human
xclog attach MyApp --human --no-colorOptions Reference
| Option | Default | Description |
|---|---|---|
--device <udid> | booted | Target simulator UDID |
--device-udid <udid> | none | Physical device UDID (show command) |
--output <file> | stdout | Also write to file |
--human | off | Human-readable colored output |
--no-color | off | Disable ANSI colors (--human mode) |
--filter <regex> | none | Filter lines by Go regex |
--subsystem <name> | none | Filter os_log by subsystem |
--max-lines <n> | 0 (unlimited) | Stop after n lines |
--timeout <duration> | 0 (unlimited) | Stop after duration (e.g. 30s, 5m) |
--last <duration> | 5m | How far back to search (show command) |
Coverage by Source
| Swift API | launch | attach | show |
|---|---|---|---|
print() | yes | no | no |
debugPrint() | yes | no | no |
NSLog() | yes | yes | yes |
os_log() | yes | yes | yes |
Logger | yes | yes | yes |
| Simulator | Physical Device | |
|---|---|---|
launch | yes | no |
attach | yes | no |
show | yes | yes |
Logger | yes | yes |
Use `launch` for full coverage. attach is for monitoring already-running processes.
Note: launch terminates any existing instance of the app before relaunching. If the app is already running and you don't want to restart it, use attach (os_log only).
Error Behavior
xclog prints errors to stderr and exits with code 1. Common errors:
| Error | Cause | Fix |
|---|---|---|
simctl launch: ... | Bad bundle ID or no booted simulator | Run xclog list to verify bundle ID; check xcrun simctl list devices booted |
could not parse PID from simctl output | App failed to launch | Check the app builds and runs in the simulator |
invalid filter regex | Bad --filter pattern | Check Go regex syntax (similar to RE2) |
invalid subsystem | Subsystem contains spaces or special characters | Use reverse-DNS format: com.example.MyApp (alphanumeric, dots, underscores, hyphens only) |
Interpreting Output
Filtering by Level
os_log levels indicate severity. For crash diagnosis, focus on error and fault.
Note: --filter matches against the message text, not the JSON output. To filter by level, use jq:
xclog launch com.example.MyApp --timeout 30s 2>/dev/null | jq -c 'select(.level == "error" or .level == "fault")'For text-based filtering, --filter works on message content:
# Filter messages containing "error" or "failed" (case-insensitive)
xclog launch com.example.MyApp --filter "(?i)error|failed"Common Subsystem Patterns
| Subsystem | What it indicates |
|---|---|
com.apple.network | URLSession / networking layer |
com.apple.coredata | Core Data / persistence |
com.apple.swiftui | SwiftUI framework |
com.apple.uikit | UIKit framework |
| App's own subsystem | Application-level logging |
Workflow: Diagnose a Runtime Crash
1. xclog list → find bundle ID 2. xclog launch <bundle-id> --timeout 60s --max-lines 500 --output /tmp/crash.log → start capture (this restarts the app — expected) 3. Reproduce the crash in the simulator 4. Read /tmp/crash.log and filter for errors: jq 'select(.level == "error" or .level == "fault")' /tmp/crash.log 5. Check the last few lines before the stream ended (crash point)
If the crash is intermittent, increase bounds: --timeout 120s --max-lines 1000 and repeat.
Workflow: Investigate Silent Failure
1. xclog launch <bundle-id> --subsystem com.example.MyApp --timeout 30s 2. Trigger the failing operation 3. Look for error-level messages in the app's subsystem 4. Cross-reference with network or data subsystems if app logs are silent
Resources
Skills: axiom-build (skills/xcode-debugging.md), axiom-performance (skills/performance-profiling.md), axiom-build (skills/lldb.md)
xcprof Reference (Structured xctrace Analysis)
xcprof turns an Instruments .trace into a structured, token-lean report for LLM consumers. It replaces the old grep-the-XML profiling pipeline: it resolves xctrace's id/ref back-references (which defeat grep), reports an honest per-family support matrix (never "no findings" when it means "couldn't measure"), and attributes CPU work to user code instead of burying it under dyld/libsystem.
Invocation
xcprof has two front-ends over the same engine — use whichever your harness provides:
- Claude Code —
xcprofis on PATH as a bare command (pluginbin/is auto-resolved). Runxcprof <subcommand>. The examples below use this CLI syntax. - MCP clients (Codex, Cursor, …) — four wrapper tools (xcprof is currently the only Axiom bin tool exposed over MCP).
| CLI subcommand | MCP tool | Required input |
|---|---|---|
xcprof doctor | axiom_xcprof_doctor | — |
xcprof analyze <trace> | axiom_xcprof_analyze | trace |
xcprof compare <baseline> <current> | axiom_xcprof_compare | baseline, current |
xcprof record | axiom_xcprof_record | one target — attach, launch, or allProcesses |
CLI flags map to camelCase MCP params: --start-ms→startMs, --end-ms→endMs, --hang-threshold-ms→hangThresholdMs, --user-binary→userBinary, --fail-on-regression→failOnRegression, --threshold-pct→thresholdPct, --allow-launch→allowLaunch, --all-processes→allProcesses, --time-limit→timeLimit, --dry-run→dryRun. The launch target -- <cmd> becomes a launch: [...] argv array.
The MCP surface is a deliberate subset: no output-format flags (--json/--both/--human — the tool returns the CLI's output as text), no --open (headless), no --no-prompt (always non-interactive). compare's regression verdict is returned in the response body rather than as exit code 3, and off-macOS the tools return a clear unavailability message instead of exit 2.
Prerequisite: run xcprof doctor
xcprof doctor verifies xcrun xctrace and counts available instruments/devices. Exit 0 ready; exit 2 if xctrace is missing.
Subcommands
xcprof record <target> [flags]— capture a new.trace, then analyze it withxcprof analyze. Picks instruments by preset, bounds the recording, and enforces security gates (see Recording below).xcprof analyze <trace> [flags]— analyze an existing.trace. Exports the TOC, thecpu-profiletable, and thenetwork-connection-stattable (when present), resolves back-references into full backtraces, and reports: summary (target, device, duration, recording mode), the support matrix, CPU hot frames (inclusive + self as % of total CPU cycles plus an approximate ms), an approximate main-thread stall signal, top user-code frames, and a network section (connections aggregated by socket: process, protocol, remote, bytes in/out). Flags:--json— compact single-line JSON (LLM-lean). Default is terse markdown.--both— markdown then JSON.--start-ms N/--end-ms N— scope analysis to a time window (the hang-window workflow: see a stall at t=2.0s, re-analyze 2000–2500ms without re-recording).--hang-threshold-ms N— main-thread gap counted as a candidate stall (default 250).--user-binary <names>— comma-separated extra binaries to treat as user code (embedded frameworks).--dsym <path>— symbolicate raw-address frames using a.dSYMbundle or Mach-O. Without it, dSYMs are auto-discovered by UUID via Spotlight; frames with no matching dSYM stay raw and are flagged.--open— open the trace in Instruments.app after analysis (opt-in; headless by default).xcprof compare <baseline> <current> [flags]— diff two traces for regressions (see Comparing traces below).xcprof doctor [--human]— environment check.
Flags may come before or after the positional <trace> arguments — xcprof handles the Go-flag positional gotcha for you.
Recording
xcprof record captures a trace and reports the saved path so you can hand it straight to analyze. A target is required — exactly one of:
--attach <pid|name>— attach to a running process (the everyday case, no gate).--all-processes— system-wide capture. Requires--allow-all-processes.-- <cmd> [args…]— launch and profile a process from startup (the command follows a literal--). Requires--allow-launch.
Instruments come from a --preset (default cpu), an explicit --template <name>, or repeated --instrument <name> (mutually exclusive — pick one source). Verified presets:
| Preset | Instruments | Use |
|---|---|---|
cpu | CPU Profiler | "slow" / CPU bottlenecks (analyze round-trips) |
memory | Allocations, Leaks | growth, retain cycles (Instruments.app only — see below) |
network | CPU Profiler, Network Connections | connections, bytes per process (analyze round-trips) |
energy | Power Profiler | battery (iOS/iPadOS only) |
full | CPU Profiler, Allocations, Leaks, Network Connections | macOS "find everything" |
full-ios | full + Power Profiler | iOS "find everything" |
Two instrument choices are deliberate, verified against real Xcode 26 exports — not guessed:
- The
cpupreset uses CPU Profiler (schemacpu-profile), not Time Profiler (time-profile/time-sample, whichanalyzedoesn't parse). - The
networkpreset uses Network Connections (schemanetwork-connection-stat— socket-level, any process), not HTTP Traffic (cfnetwork tables that only populate for URLSession traffic andanalyzedoesn't read).
Allocations/Leaks stay in the memory/full presets so the recording is viewable in Instruments.app, but analyze can't surface their data (see Honesty caveats).
xcprof record --preset cpu --attach MyApp --time-limit 10s # attach (no gate)
xcprof record --allow-launch --time-limit 10s -- /path/to/MyApp # launch
xcprof record --preset cpu --attach MyApp --dry-run # preview the exact xctrace commandrecord emits compact JSON by default (the saved trace path, resolved instruments, a structured target_mode of attach/launch/all_processes, effective time_limit, and the full command for transparency); --human for terminal text.
Security gates (designed up front, not bolted on)
- Bounded by default.
--max-duration(default60s) is a hard ceiling; an unset--time-limitadopts it, so a recording is never unbounded. A--time-limitabove the ceiling is refused — raise--max-durationto record longer. - `--allow-launch` is required before
-- <cmd>will execute anything. - `--allow-all-processes` is required before system-wide capture.
- Output sandbox.
--outputmust resolve underXCPROF_TRACE_ROOT(or cwd when unset); an outside path is refused unless--allow-external-outputis passed. - `--no-prompt` is needed for non-interactive use (otherwise xctrace's privacy prompt can stall). Pass it from agents.
record honesty caveat
A --launch recording terminated at the time limit makes xctrace exit non-zero (it returns the killed target's status) while still saving a valid trace. record trusts the saved bundle, not the exit code: it reports ok: true with a notes entry explaining the benign non-zero exit.
Comparing traces (regression detection)
xcprof compare <baseline> <current> runs the analyze pipeline on each trace and diffs them into function-level deltas — for "did this change regress CPU?" and for CI gating. It matches frames by (binary, function name) and reports the change in inclusive CPU-cycle share in percentage points (the comparable quantity across two runs of different total work — raw cycles and ms are not).
- A frame whose inclusive share rose by ≥
--threshold-pct(default5) is a regression; fell by ≥ that, an improvement; in between is noise and dropped. Lists sort byseverity=|incl_pct_delta| × max(baseline,current inclusive ms)(the "% delta × absolute time" rank). --fail-on-regressionexits 3 when any regression meets the threshold — the CI gate (distinct from2usage /8I/O so an agent can tell "slower" from "broke").- Defaults to compact JSON;
--humanfor markdown,--both(markdown then JSON).--dsymapplies to both traces. (No--user-binary/window flags: compare diffs the hot-frame tables over the full trace.)
xcprof compare baseline.trace current.trace --human # read the diff
xcprof compare baseline.trace current.trace --fail-on-regression --threshold-pct 5 # CI gate (exit 3 on regression)Both recordings must exercise the same workload or the deltas measure workload differences, not code changes. Raw-address frames (0x…) don't match across builds (ASLR) — they're excluded and counted in a note; pass --dsym for symbol-level deltas on release builds. Per-connection network matching is unreliable across runs, so only total rx/tx byte deltas are reported. Full workflow + CI recipe: axiom-performance (skills/trace-comparison.md).
Honesty caveats
- Frame cost is cycle share, not time. The
%is the exact share of total CPU cycles; themsfigure is an approximate wall-time from the frame's sample share × the analyzed window. Cycle-weight is cycles (the export's "Cycles" column), and cycles→time needs per-core frequency under DVFS that the trace doesn't carry — so ms is never derived from cycles. - Main-thread stalls are approximate. cpu-profile samples only running threads, so a large inter-sample gap is a candidate stall, not a confirmed hang — the Hangs instrument confirms (a later xcprof phase).
- Release builds show addresses. Stripped binaries report raw
0x…frame names. xcprof resolves them via--dsym <path>or auto-discovery by UUID through Spotlight; frames with no matching dSYM stay raw and are flagged (never invented). Fuller discovery sources (Archives/DerivedData walks, shared with xcsym) come with a later engine-extraction phase. Debug builds symbolicate natively, and Instruments may pre-symbolicate the export when it can find the dSYM at record time. - Support matrix is honest about what xctrace can export.
cpuandnetworkparse (availablewhen data is present,partialwhen the table is present but empty). memory and macOS energy reportnot_exportable: Allocations/Leaks data lives in the trace's event store (no XML table — open it in Instruments.app), and Power Profiler is iOS/iPadOS-only and unsupported on macOS. A family genuinely absent from the recording isnot_present. None of these ever reads as a silent "clean". (On-device iOS energy parsing is a future, device-verified addition.)
Output & exit codes
analyze: compact JSON (--json) or terse markdown (default); --both for both. record, compare, and doctor: compact JSON by default, --human for text. (analyze defaults to the human-readable report because it's a read-oriented analysis; record/compare/doctor default to JSON because they're scriptable status steps — the same split the rest of the toolkit follows: machine format is always compact JSON, --human is the prose escape.) Exit 0 ok · 2 environment/usage error (xctrace missing, trace not found, bad args, refused security gate) · 3 compare regression met --threshold-pct under --fail-on-regression · 8 output-write error.
Scope
Shipped: doctor, analyze (CPU cpu-profile family with --dsym symbolication — explicit path + Spotlight auto-discovery — plus the network-connection-stat socket family), record (presets + bounded duration + launch/all-processes/output security gates), and compare (function-level CPU-share regression detection with a --fail-on-regression CI gate). memory and macOS energy are not_exportable by design (data not surfaced by xctrace export). The shared dSYM/symbolication engine with fuller discovery, on-device iOS energy parsing, and cleanup remain later phases.
Resources
Tools: xcrun xctrace (Instruments CLI), companion tools xclog, xcsym, xcui
Skills: axiom-performance
xcsym Reference (iOS/macOS Crash Symbolication)
xcsym symbolicates .ips (v1/v2), MetricKit (MXCrashDiagnostic), Apple's legacy .crash text reports, and Xcode Organizer .xccrashpoint bundles end-to-end and emits LLM-friendly JSON. It auto-detects format, discovers dSYMs from Archives/DerivedData/downloads, symbolicates frames via atos, categorizes the crash into a pattern_tag, and reports UUID/arch mismatches per image. Single binary, no dependencies beyond Xcode CLT.
.crash text is the format Xcode Organizer exposes when a user chooses "Show in Finder" on a TestFlight crash. .xccrashpoint bundles nest .crash files under Filters/Filter_<id>/Logs/ (with optional LocallySymbolicated/ siblings). Point xcsym at either the bundle path or the inner .crash directly — bundle inputs are walked automatically: the Filter dir with the most recent modification time wins, and the raw .crash is preferred over LocallySymbolicated/.
Invocation
xcsym is on PATH as a bare command (Claude Code 2.1.91+ resolves plugin bin/ entries automatically). Just run xcsym <subcommand> — no prefix, no path lookup.
When to Use
- Triaging a new `.ips` — full pipeline in one call, structured JSON out
- TestFlight crashes — paired with
xcsym verifyto diagnose UUID mismatches - MetricKit crashes — write
MXCrashDiagnostic.jsonRepresentation()to disk and runcrash - Explaining why a crash is unsymbolicated —
verifytells you per-image UUID/arch mismatch - Inventorying local dSYMs —
list-dsymsenumerates archives + DerivedData - Scrubbing a user's crash for a fixture —
anonymizepreserves dSYM UUIDs (correlation keys) while scrubbing PII
Building an in-app crash reporter? That's the CrashReportExtension framework, not xcsym — see axiom-performance (skills/metrickit-ref.md Part 10).
Do not use `xcsym crash` for hangs. crash rejects .ips files with bug_type=298 (exit 1, "error":"hang_report" on stdout). Use xcsym triage with kind: "hang" in the NormalizedReport for hang classification, or see axiom-performance (skills/hang-diagnostics.md) for single-hang investigation.
Critical Best Practices
Start with `crash`. It runs the full pipeline (parse → discover dSYMs → symbolicate → categorize → emit JSON). Only reach for resolve, find-dsym, or verify when crash surfaces a specific problem.
Read `pattern_tag` first. It's the most compact signal about what kind of crash you're looking at. Map it to the agent's fix-guidance table before reading frames.
Trust exit codes. Non-zero codes say why symbolication was incomplete — don't assume a crashed call means the tool failed.
Anonymize before committing a fixture. The anonymize subcommand is format-aware (handles .ips v1/v2, MetricKit, and legacy .crash text) and intentionally preserves dSYM UUIDs so anonymized fixtures still symbolicate against your dSYMs.
Subcommands
crash — Full Pipeline
xcsym crash --format=summary <file> # small tier (~2KB target, warns past 4KB)
xcsym crash --format=standard <file> # default (~12KB target, warns past 50KB)
xcsym crash --format=full <file> # all threads (warns past 100KB)
xcsym crash --from-metrickit <file> # force MetricKit (skip auto-detect)
xcsym crash --dsym <path> <file> # explicit dSYM for the main app
xcsym crash --dsym-paths <a>:<b> <file> # extra dSYM search roots
xcsym crash --no-symbolicate <file> # skip atos; keep raw frames
xcsym crash --no-cache <file> # bypass UUID cache
xcsym crash --no-spotlight <file> # skip mdfind lookups
xcsym crash --no-defaults <file> # skip Archives/DerivedData/Downloads/Toolchain/Frameworks(cwd) walks (fast triage)
xcsym crash --output <path> <file> # write JSON to a file
xcsym crash --human <file> # terse prose summary instead of JSON (for a person)
xcsym crash - < crash.ips # read from stdin (for pasted content)
xcsym crash crash.crash # legacy Apple text format (Organizer export)
xcsym crash Foo.xccrashpoint # Xcode Organizer bundle (auto-walks to inner .crash)
xcsym crash --filter 0.8.60-Any Foo.xccrashpoint # bundle with multiple Filter_* dirs: pick the one whose name contains this substring (use a dash-bounded fragment to avoid matching "1.0" against "11.0.0")
xcsym crash --prefer-locally-symbolicated Foo.xccrashpoint # use Logs/LocallySymbolicated/*.crash instead of rawAccepted inputs: .ips (v1 and v2 JSON), MetricKit MXCrashDiagnostic JSON, Apple's legacy .crash text format, and .xccrashpoint directory bundles. The file extension doesn't matter for non-bundle inputs — format is auto-detected from content. The .xccrashpoint suffix is matched case-insensitively (APFS/HFS+ are case-insensitive by default). For .xccrashpoint bundles, xcsym walks Filters/Filter_*/Logs/ and picks one .crash file: the Filter dir with the most recent modification time (override with --filter <substring> — substring, not segment-anchored, so prefer dash-bounded fragments like 0.8.60-Any), raw copy preferred over LocallySymbolicated/ (override with --prefer-locally-symbolicated to keep Xcode's atos output instead of re-symbolicating). The original bundle path is surfaced in input.bundle so consumers can tell where the resolved .crash came from.
Unsupported input returns exit 2 with a structured JSON reject on stdout ({"error":"unsupported_format", …}) so agents can route on the error field instead of scraping stderr. See the Exit Codes section below.
Output is compact JSON by default (single-line, token-lean for LLM consumers); every report subcommand (crash, resolve, find-dsym, list-dsyms, verify) takes --human for a terse prose rendering, and … | jq . gives indented JSON. anonymize is the exception — its output is a crash document in the .ips wire format (compact header line + pretty payload), not a report, so it has no --human.
Flag placement matters. Go's flag package stops parsing at the first positional, so flags must come before the file path. xcsym crash <file> --format=summary fails with a usage error.
verify — dSYM Match Diagnostics
xcsym verify <file>
xcsym verify <file> --dsym <path>
xcsym verify <file> --dsym-paths <a>:<b>
xcsym verify <file> --no-cache
xcsym verify <file> --no-spotlightReports which images are matched, mismatched (UUID or arch), and missing. Use when crash exits non-zero to pinpoint which dSYM is wrong.
resolve — Single-Address Resolution
xcsym resolve --dsym <path> --load-addr <hex> <addr>...
xcsym resolve --dsym /bin/ls --load-addr 0x100000000 0x10000aabb 0x10000bbcc
xcsym resolve --dsym <path> --load-addr <hex> --arch arm64 <addr>...Hands raw addresses to atos against a specific dSYM. Useful for one-off address resolution outside a crash context.
find-dsym — Locate dSYM by UUID
xcsym find-dsym <uuid>
xcsym find-dsym <uuid> --arch arm64
xcsym find-dsym <uuid> --dsym-paths <a>:<b>
xcsym find-dsym <uuid> --no-cache
xcsym find-dsym <uuid> --no-spotlightWalks the same discovery chain as crash minus the per-UUID explicit map (step 1) — see "dSYM Discovery Order" below for the authoritative order.
list-dsyms — Inventory
xcsym list-dsyms
xcsym list-dsyms --source=archives # only Archives
xcsym list-dsyms --source=deriveddata # only DerivedData
xcsym list-dsyms --source=downloads
xcsym list-dsyms --source=toolchain
xcsym list-dsyms --source=frameworks
xcsym list-dsyms --source=env
xcsym list-dsyms --source=all # default
xcsym list-dsyms --dsym-paths <a>:<b>triage — Corpus Classification
xcsym triage < corpus.jsonl # read NormalizedReport JSONL from stdin
xcsym triage corpus.jsonl # or from a file
xcsym triage --latest-version 2.1.1 < corpus.jsonl # flag issues older than this version
xcsym triage --os-floor 18.0 < corpus.jsonl # flag issues below this OS floor
xcsym triage --min-users 5 < corpus.jsonl # flag issues with fewer affected users
xcsym triage --latest-version 2.1.1 --os-floor 18.0 --min-users 5 < corpus.jsonl # all thresholdsInput: one NormalizedReport JSON object per line (JSONL). Each report describes one grouped issue from Sentry or App Store Connect — provider, issue_id, kind (crash or hang), impact, threads[] with frames[] where each frame carries in_app. See axiom-shipping (skills/production-triage.md) for the full schema and provider fetch workflow.
Output: a single TriageResult JSON object to stdout with:
summary— total/crashes/hangs/skipped/clusters/flagged_noise/candidate_familiesissues[]— per-issuepattern_tag,pattern_confidence,pattern_rule_id,cluster_key,cluster_confidence,noise_flags[],top_framesclusters[]— mechanical groupings by signature withcluster_key,cluster_confidence,dominant_pattern_tag,issue_ids[],total_users,total_eventserrors[]— malformed or unclassifiable reports (run still exits 0)
Network-free. No symbolication, no dSYM discovery, no atos, no environment capture. Provider-symbolicated frames arrive via the NormalizedReport.
Accepts hangs. Unlike xcsym crash, triage accepts kind: "hang" and classifies them with anr_idle_runloop / anr_main_thread_block tags. The noise.anr_suspension.v1 rule automatically flags idle-runloop hangs as likely background suspension false-positives.
Exit codes: 0 = success (including "some reports skipped" — see errors[]); 1 = usage error / unreadable stream / invalid flags; 8 = output write error. Never non-zero for "found noise."
anonymize — Scrub PII for Fixtures
xcsym anonymize <file> # anonymized content to stdout (.ips, MetricKit, or .crash)
xcsym anonymize --output <path> <file> # write to file
xcsym anonymize - < crash.ips # read from stdinScrubs:
- Bundle IDs across every spelling (
bundle_id,bundleID,bundleIdentifier,CFBundleIdentifier,codeSigningID,coalitionName) - Process and app names (
procName,app_name) - User paths (
/Users/<name>/→/Users/REDACTED/) .appnames (word-boundary regex, socom.apple.*identifiers aren't mangled) and.frameworknames (anchored to end-of-string or/, socom.framework.*reverse-DNS segments aren't mangled)- IPv4 and IPv6 addresses
- Device names and account IDs (
crashReporterKey,sessionID,incident_id,incident,deviceIdentifier,deviceUDID,userID) - Binary names inside
usedImages[].nameand MetricKitbinaryName - Foreign UUIDs in freeform strings (incident IDs, paths)
.crashheader keys that always carry PII:Process,Identifier,Parent Process,Coalition,Terminating Process,Hardware Model,AppVariant— rewritten to deterministic placeholders while preserving column padding so a human can sanity-check the result
Preserves:
- dSYM UUIDs —
slice_uuid,usedImages[].uuid, MetricKitbinaryUUID— so anonymized output still symbolicates against matching dSYMs - Thread names (
threads[].name, e.g.com.apple.main-thread) — Apple infrastructure labels, not PII; keeping them preserves debug context - Library identifiers inside nested library structures (same rationale as thread names)
- Structural fields categorize and symbolicate rules depend on (exception type, codes, subtype, thread state, frame offsets)
Output Schema
Top-level JSON emitted by crash:
{
"tool": "xcsym",
"version": "0.1.0-dev",
"format": "standard",
"environment": {
"atos_version": "...",
"clt_version": "...",
"xcode_path": "/Applications/Xcode.app"
},
"input": {
"path": "testdata/crashes/ips_v2/swift_forced_unwrap.ips",
"format": "ips_json_v2", // one of: ips_json_v1 | ips_json_v2 | metrickit_json | apple_crash_text
"bundle": "/path/to/Foo.xccrashpoint" // omitted unless input was a .xccrashpoint bundle
},
"crash": {
"app": { "name": "...", "version": "...", "bundle_id": "..." },
"os": { "platform": "iOS", "version": "17.5", "is_simulator": false },
"arch": "arm64",
"exception": { "type": "EXC_BREAKPOINT", "codes": "0x1", "subtype": "...", "signal": "SIGTRAP" },
"termination": { "namespace": "SIGNAL", "code": "0x5" },
"pattern_tag": "swift_forced_unwrap",
"pattern_confidence": "high",
"pattern_rule_id": "R-swift-unwrap-01",
"pattern_reason": "exception.subtype matched '...unexpectedly found nil...'",
"crashed_thread": { "index": 0, "triggered": true, "frames": [...] },
"other_threads_top_frames": [...],
"all_threads": [...]
},
"images": { "matched": [...], "mismatched": [...], "missing": [...] },
"images_summary": { "matched_count": 1, "mismatched_count": 0, "missing_count": 0 },
"warnings": [],
"size_warning": "report size 54321 bytes exceeds 51200 bytes; consider --format=summary for triage"
}Tiers
| Tier | Design target | Warns past | Contains |
|---|---|---|---|
summary | ~2 KB | 4 KB | App, OS, exception, pattern_tag, crashed-thread top 3 frames, images_summary |
standard | ~12 KB | 50 KB | + full crashed thread, other threads' top frames, images |
full | n/a | 100 KB | + all_threads (every thread, every frame) |
Design targets are aspirational — small/typical crashes hit them. Real production crashes from framework-heavy apps regularly exceed them without indicating anything pathological (the images array dominates standard size: ~150–300 bytes per image × 45–150 images is typical). The warn threshold is what xcsym actually flags via size_warning in output.
Exit Codes
Exit codes are subcommand-specific. Usage errors, tool errors, timeouts, and output errors are shared across all subcommands. Symbolication-specific codes (2/3/4/7) vary in meaning between crash and verify.
Shared across all subcommands:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Usage error (bad flags, missing required args) |
| 5 | Tool/discovery error (dwarfdump/atos failed, Spotlight failed, etc.) |
| 6 | Command timeout |
| 8 | Output write error (e.g., --output path unwritable) |
`crash` — main-image-centric:
| Code | Meaning | First thing to do |
|---|---|---|
| 0 | All images matched | — |
| 2 | Input not found / unreadable / unsupported format OR main app dSYM missing | Check path exists; otherwise download the dSYM for the main UUID |
| 3 | Main app UUID mismatch | xcsym find-dsym <uuid> against the exact UUID from the crash |
| 4 | Main app arch mismatch | User is on a different slice (arm64e vs arm64); use find-dsym --arch |
| 7 | Main matched, some other images missing/mismatched | Partial success — frames in the main binary symbolicate, others won't |
`verify` — per-image-centric (note the 7 vs crash difference):
| Code | Meaning |
|---|---|
| 2 | Input not found / unreadable / unsupported format |
| 3 | Any image has a UUID mismatch with an explicitly-overridden dSYM |
| 4 | Any image has an arch-slice mismatch with its dSYM |
| 7 | Any missing images (with or without matches — NOT "main matched + others missing") |
`find-dsym` — lookup-centric:
| Code | Meaning |
|---|---|
| 0 | Match — dSYM located |
| 2 | Miss — nothing found across every discovery source |
`list-dsyms`, `resolve`, `anonymize`: success/failure only (0/1/5/6/8); no symbolication-specific codes.
On hang input (bug_type=298), crash exits 1 after writing a JSON reject to stdout of shape {"tool":"xcsym","error":"hang_report","message":"...","input":"...","routing":"..."}. Route the user to hang-diagnostics when you see "error":"hang_report".
On unsupported input (anything that isn't .ips, MetricKit, Apple .crash text, or a .xccrashpoint bundle), crash exits 2 after writing {"tool":"xcsym","error":"unsupported_format","message":"...","input":"...","routing":"..."} to stdout. The routing field names the accepted formats.
A .xccrashpoint directory that exists but doesn't contain Filters/Filter_*/Logs/*.crash is a distinct error — crash exits 2 with {"error":"empty_bundle","routing":"..."}. Agents should route the two cases separately: unsupported_format means the user gave xcsym the wrong file type; empty_bundle means the bundle is right but corrupt or stripped (re-export from Xcode Organizer).
If the resolver hits a real I/O error walking the bundle (permission denied, stale NFS handle, etc.), crash exits 5 (tool error) rather than mislabeling the bundle as empty.
Pattern Tag Catalog
Every pattern_tag xcsym can emit, with the rule that fires it:
| pattern_tag | Rule ID | Confidence | Signal |
|---|---|---|---|
swift_forced_unwrap | R-swift-unwrap-01 | high | Subtype contains "unexpectedly found nil..." |
swift_concurrency_violation | R-swift-conc-01 | high | _swift_task_isCurrentExecutor in subtype |
swift_fatal_error | R-swift-fatal-01 | high | Swift runtime failure + _fatalError / _preconditionFailure / _assertionFailure sentinel frame |
zombie_or_heap_corruption | R-zombie-01 | heuristic | libgmalloc / NSZombie image in the crashed thread |
stack_overflow | R-stack-overflow-01 | heuristic | KERN_PROTECTION_FAILURE with fault within 1 page of SP |
bad_memory_access | R-bad-access-01 | high | EXC_BAD_ACCESS with KERN_INVALID_ADDRESS |
illegal_instruction | R-illegal-inst-01 | high | EXC_BAD_INSTRUCTION |
exc_guard | R-exc-guard-01 | high | EXC_GUARD |
objc_exception | R-objc-exc-01 | high | EXC_CRASH/SIGABRT with objc_exception_throw frame |
main_thread_checker_violation | R-mtc-01 | high | main_thread_checker.dylib in crashed frames |
abort | R-abort-01 | high | SIGABRT with abort/__abort_with_payload frame |
watchdog_termination | R-watchdog-01 | high | Termination namespace FRONTBOARD/SPRINGBOARD/ASSERTIOND + code 0x8BADF00D |
user_force_quit | R-user-quit-01 | high | FRONTBOARD + 0xDEADFA11 |
background_task_expired | R-bg-expired-01 | high | code 0xBAADCA11 (any namespace) |
data_protection_violation | R-data-prot-01 | high | code 0xdead10cc (any namespace) |
code_signing_killed | R-code-sign-01 | high | code matches 0xc51bad0[0-9a-f] (case-insensitive, any namespace) |
jetsam_oom | R-jetsam-01 | high | EXC_RESOURCE with MEMORY subtype OR termination.reason contains per-process-limit / vm-pageshortage |
cpu_resource_fatal | R-cpu-fatal-01 | high | EXC_RESOURCE CPU/WAKEUPS FATAL (excludes NON-FATAL) |
swiftui_update_loop | R-swiftui-loop-01 | low | ≥100 consecutive AG::Graph::update_* frames from the top |
unclassified | — | low | No rule matched — raw fields are in pattern_reason |
dSYM Discovery Order
Source: tools/xcsym/dsym.go. Sources are tried first-hit-wins in this exact order:
1. ExplicitByUUID — per-image overrides the crash subcommand builds when the header lists a main-image UUID (before any other source, including cache) 2. Explicit paths — --dsym direct override and --dsym-paths extra roots 3. UUID cache — ~/Library/Caches/xcsym/uuid-index.json (skip with --no-cache) 4. Spotlight — mdfind kMDItemContentType == com.apple.xcode.dsym (skip with --no-spotlight) 5. Archives — ~/Library/Developer/Xcode/Archives/** (most recent first) 6. DerivedData — ~/Library/Developer/Xcode/DerivedData/**/Build/Products/** 7. Frameworks (cwd scan) — walks the current working directory plus caller-supplied roots for *.xcframework, Carthage/Build, and Pods layouts. Bounded by XCSYM_FRAMEWORK_SCAN_TIMEOUT (Go duration or integer seconds; default 500ms) so an unrelated monorepo checkout can't stall discovery. An exhausted budget is swallowed as "no match" and the chain continues. 8. Downloads — ~/Downloads/** (for drag-and-dropped App.dSYM.zip files) 9. Toolchain — current Xcode toolchain (system Swift dylibs bundled with Xcode.app) 10. Env paths — XCSYM_DSYM_PATHS (colon-separated, processed as a last-resort supplement to --dsym-paths)
find-dsym follows the same chain minus step 1 (no per-UUID explicit map). list-dsyms --source=<name> restricts scanning to a single root by name.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Exit 2, "main dSYM missing" | No archive for that UUID on this machine | Download the archive from App Store Connect; or set XCSYM_DSYM_PATHS to its location |
| Exit 3, main UUID mismatch | Crash came from a different build than the archive on disk | xcsym find-dsym <uuid> against the exact UUID from the crash |
| Exit 4, main arch mismatch | arm64 vs arm64e slice mismatch | Pass --arch to find-dsym; verify the archive contains the slice |
| Exit 7, "main matched, others missing" | Third-party frameworks shipped without dSYMs | Expected for stripped dependencies; main app frames symbolicate |
Exit 2 with empty_bundle on .xccrashpoint | Bundle has no Filters/Filter_*/Logs/*.crash (corrupt or stripped) | Pull a fresh export from Xcode Organizer; or point xcsym at a specific .crash inside the bundle |
Exit 5 on .xccrashpoint (tool error) | Permission denied / stale mount walking Filters/ | Check mount health and ls -la Foo.xccrashpoint/Filters |
.xccrashpoint with multiple builds returns the wrong one | Default picks the Filter dir with the most recent modification time | Use --filter <substring> to select a specific build by version/platform — prefer dash-bounded fragments like 0.8.60-Any |
pattern_tag="unclassified" | No rule matched | Read pattern_reason for inspected fields; file a gap report |
size_warning in output | Tier exceeded its warn threshold (4 KB summary / 50 KB standard / 100 KB full) | Switch to the next smaller tier — the warning text names it |
{"error":"hang_report"} on stdout, exit 1 | .ips is a hang (bug_type=298), not a crash | Use hang-diagnostics skill; crash rejects hangs by design |
crash/verify takes minutes on a long-lived dev machine | Per-image walks of a huge ~/Library/Developer/Xcode/DerivedData/** (the --no-cache/--no-spotlight flags don't skip these) | Add --no-defaults to bypass all default search roots (Archives/DerivedData/Downloads/Toolchain/Frameworks(cwd)) — symbolicates from --dsym/--dsym-paths/XCSYM_DSYM_PATHS only; images without those report Missing |
Resources
Skills: axiom-tools (skills/xclog-ref.md), axiom-build (skills/lldb.md, skills/lldb-ref.md, skills/xcode-debugging.md), axiom-performance (skills/memory-debugging.md, skills/metrickit-ref.md, skills/hang-diagnostics.md), axiom-shipping (skills/testflight-triage.md, skills/production-triage.md, skills/app-store-diag.md, skills/app-store-submission.md)
Agents: crash-analyzer (single crash file: xcsym crash + pattern_tag → fix guidance), triage-analyzer (corpus triage: fetch Sentry/ASC → xcsym triage → ranked report), simulator-tester (auto-runs xcsym on crashes during test runs), test-failure-analyzer + test-debugger (symbolicate test-generated .ips artifacts), memory-auditor (correlates jetsam/heap-corruption tags with leak patterns), concurrency-auditor (correlates swift_concurrency_violation with @MainActor gaps), energy-auditor (correlates CPU/watchdog/background terminations with energy anti-patterns)
Commands: /axiom:analyze-crash (single crash), /axiom:triage (corpus triage)
xcui Reference (Scriptable Simulator UI & Accessibility Testing)
xcui makes iOS-simulator UI and accessibility testing scriptable for coding harnesses. It owns the test-harness semantics AXe and simctl lack — waiting, asserting, accessibility config, dialogs, computed VoiceOver — and delegates input (tap/type/swipe) to AXe, which injects real HID touch.
Invocation
xcui is on PATH as a bare command (plugin bin/ is auto-resolved). Run xcui <subcommand>.
Prerequisite: run xcui doctor
xcui doctor verifies AXe (the input/tree engine), Homebrew, Xcode, and a booted sim. If AXe is missing and brew is present, xcui doctor --install runs brew install cameroncooke/axe/axe (explicit/consented — never silent). Exit 0 = ready; exit 2 = AXe missing or no booted sim (see problems/next_steps in the JSON). When several sims are booted, every verb targets the lowest UDID deterministically; doctor adds a note listing them, and --udid <id> (accepted by every verb, doctor included) targets a specific one.
Subcommands
xcui wait --for-element <id> | --gone <id> | --idle [--timeout 10s] [--poll 250ms]— poll the a11y tree until a condition holds. Replaces sleep/re-screenshot guesswork (CLIwaitForExistence).xcui assert --id <id> [--label <s>] [--value <s>] [--trait <role>] [--single]— assert on an element.--singlechecks the id resolves to exactly one element (e.g. "hero announces as one element").xcui a11y set --toggle <name> --value <on/off> [--app <bundle-id>]— set an accessibility setting. Supported toggles (all verified against the simulator):dynamic-type— nativesimctl ui content_size;--valueis a size (large,accessibility-extra-large, … up toaccessibility-extra-extra-extra-large). Applies live; no relaunch.increase-contrast— nativesimctl ui increase_contrast;--valueison/off. Applies live; no relaunch.reduce-motion—defaults write com.apple.Accessibility ReduceMotionEnabled; needs relaunch, so pass--app <bundle-id>to have xcui terminate + relaunch the app.reduce-transparency—defaults write com.apple.Accessibility ReduceTransparencyEnabled; needs relaunch (pass--app).xcui a11y reset— clear xcui-set overrides (delete the defaults keys, content_size → large, increase_contrast → disabled).xcui dialog accept | dismiss [--udid <udid>]— find the frontmost system alert and tap the right button:acceptprefers the most-permissive standard grant (Allow While Using App›Allow Once›Allow›OK›Open),dismissprefers the decline (Don't Allow›Cancel›Not Now). A one-button alert is tapped for either intent. Matching is case- and apostrophe-insensitive (curly’= straight'). The tap delegates toaxe tap(by id when present, else by label). Exit0handled,1no actionable alert.xcui dialog pregrant <bundle-id> <service>… [--udid <udid>]— grant permissions ahead of time viasimctl privacy … grant, so the dialog never appears. Services aresimctl privacynames (camera,photos,location,microphone,contacts, …). Prefer this overacceptwhen you control the test setup — no alert means nothing to race.xcui voiceover traverse [--udid <udid>]— emit the computed VoiceOver announcement sequence: walk the a11y tree in focus order (top-to-bottom, leading-to-trailing) and render each focusable element aslabel, value, trait(plusdimmedwhen disabled). Output is asequenceJSON array.xcui voiceover assert --sequence <file> [--udid <udid>]— compare the live announcement sequence to an expected one; the file may be a bare JSON string array or a savedtraversereport (it round-trips). Reports every differing index (one entry per mismatched position, plus a length-mismatch note when counts differ); exit1on any mismatch.
VoiceOver scope (honest framing): voiceover renders the computed announcement from the accessibility tree — what VoiceOver would say, derived deterministically. It is not captured audio/TTS, which the simulator does not expose to scripting. Use it to catch missing labels, wrong trait phrasing, bad focus order, and unannounced state — not to verify the speech synthesizer itself.Not yet supported (a11y toggles): thevoiceover,differentiate-without-color, andbold-texttoggles fora11y sethad no confirmable simulator mechanism (no nativesimctl uisetter, and their candidatedefaultskeys are not populated/honored by iOS on the sim). They are intentionally omitted from v1 rather than shipped unverified. (This is distinct from thexcui voiceovercommand above, which reads the tree and needs no toggle.)
For input, use AXe directly
axe tap --id loginButton --udid <udid> # real HID touch, not pointer-hover
axe type "user@example.com" --udid <udid>
axe describe-ui --udid <udid> # raw a11y tree (xcui assert/wait parse this)Output & exit codes
JSON by default (tool/version envelope); --human for prose. Exit: 0 pass · 1 assertion-fail/wait-timeout · 2 environment error · 8 output-write error.
CLI gotcha: Go's flag parser stops at the first positional, so always put flags after the subcommand and use the all-flag forms shown above (assert --id …, notassert <id> …).
Resources
Tools: axe (AXe — brew install cameroncooke/axe/axe), xcrun simctl
Skills: axiom-accessibility, axiom-testing
Agents: simulator-tester (drives xcui live), accessibility-auditor (static a11y scan)
Related skills
How it compares
Use axiom-tools over generic iOS build skills when the task needs Axiom-specific console, crash, UI, or profiling CLIs.
FAQ
Which Axiom tools does axiom-tools cover?
axiom-tools documents four Axiom CLIs: xclog for console capture, xcsym for .ips and crash symbolication, xcui for simulator UI and accessibility, and xcprof for xctrace and CPU profile analysis.
When should iOS developers invoke axiom-tools?
Invoke axiom-tools when routing Swift agent work through Axiom for simulator logs, symbolicated crashes, UI validation, or performance traces instead of manual Xcode-only workflows.
Is Axiom Tools safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.