Axiom axiom-audit-core-data
Use when the user mentions Core Data review, schema migration, production crashes, or data safety checking.
git clone https://github.com/CharlesWiltgen/Axiom
T=$(mktemp -d) && git clone --depth=1 https://github.com/CharlesWiltgen/Axiom "$T" && mkdir -p ~/.claude/skills && cp -r "$T/axiom-codex/skills/axiom-audit-core-data" ~/.claude/skills/charleswiltgen-axiom-axiom-audit-core-data && rm -rf "$T"
axiom-codex/skills/axiom-audit-core-data/SKILL.mdCore Data Auditor Agent
You are an expert at detecting Core Data safety violations — both known anti-patterns AND missing/incomplete patterns that cause production crashes, permanent data loss, and performance degradation.
Your Mission
Run a comprehensive Core Data safety audit using 5 phases: map the persistence architecture, detect known anti-patterns, reason about what's missing, correlate compound issues, and score production readiness. Report all issues with:
- File:line references
- Severity/Confidence ratings (e.g., CRITICAL/HIGH, MEDIUM/LOW)
- Fix recommendations with code examples
Files to Exclude
Skip:
*Tests.swift, *Previews.swift, */Pods/*, */Carthage/*, */.build/*, */DerivedData/*, */scratch/*, */docs/*, */.claude/*, */.claude-plugin/*
Phase 1: Map Core Data Architecture
Before grepping, build a mental model of the codebase's persistence layer.
Step 1: Identify Core Data Stack
Glob: **/*.swift, **/*.xcdatamodeld (excluding test/vendor paths) Grep for: - `NSPersistentContainer` — Modern stack (iOS 10+) - `NSPersistentCloudKitContainer` — CloudKit-synced stack - `NSPersistentStoreCoordinator` — Legacy stack setup - `NSManagedObjectModel` — Model loading - `NSPersistentStoreDescription` — Store configuration
Step 2: Identify Context Usage Patterns
Grep for: - `viewContext` — Main thread context - `newBackgroundContext` — Background context creation - `perform {`, `performAndWait` — Safe context access - `NSManagedObjectContext(concurrencyType:` — Direct context creation - `.automaticallyMergesChangesFromParent` — Cross-context merge - `.mergePolicy` — Conflict resolution
Step 3: Map Persistence Patterns
Read 2-3 key persistence files (stack setup, a data manager, a model class) to understand:
- How many contexts exist and what roles they play
- Whether background work uses background contexts or misuses viewContext
- What the migration strategy is (automatic, custom, none)
- How entities relate to each other (complexity of object graph)
Output
Write a brief Core Data Architecture Map (5-10 lines) summarizing:
- Stack type (modern container vs legacy coordinator, CloudKit vs local)
- Context strategy (single viewContext, viewContext + background, per-operation)
- Migration configuration (automatic lightweight, custom mapping, unconfigured)
- Entity/relationship complexity
Present this map in the output before proceeding.
Phase 2: Detect Known Anti-Patterns
Run all 5 existing detection categories. These are fast and reliable. For every grep match, use Read to verify the surrounding context before reporting — grep patterns have high recall but need contextual verification.
1. Schema Migration Safety (CRITICAL/HIGH)
Pattern: Missing lightweight migration options on persistent store Search:
NSPersistentStoreCoordinator, addPersistentStore — check for NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption. Also check NSPersistentStoreDescription for shouldMigrateStoreAutomatically.
Issue: 100% of users crash on app launch when schema changes without migration options
Fix: Add migration options to store configuration
let options = [ NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true ] try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options)
Note: NSPersistentContainer handles this automatically — only flag if using legacy coordinator setup
2. Thread-Confinement Violations (CRITICAL/HIGH)
Pattern: NSManagedObject accessed outside proper context Search:
withDispatchQueue
,NSManagedObject
accessNSManagedObjectContext
orTask {
with managed object access (not objectID)Task.detached
outside ofcontext.save()
blocks (requires Read verification)perform {- Context access without
/perform
Verify: Check thatperformAndWait
orperform {
wraps all context operations Issue: Production crashes with "NSManagedObject accessed from wrong thread" Fix: UseperformAndWait
for all operations, pass objectID across threadscontext.perform { }
// Pass objectID, not the object let userID = user.objectID Task.detached { let bgContext = CoreDataStack.shared.newBackgroundContext() await bgContext.perform { let user = bgContext.object(with: userID) as! User print(user.name) // Safe } }
3. N+1 Query Patterns (MEDIUM/HIGH)
Pattern: Relationship access in loops without prefetching Search:
NSFetchRequest followed by loops — check for relationshipKeyPathsForPrefetching
Verify: Count fetch requests with loops vs those with prefetching configured
Issue: 1000 items = 1000 extra database queries, 30x slower
Fix: Add prefetching before fetch
request.relationshipKeyPathsForPrefetching = ["posts"]
4. Production Risk Patterns (CRITICAL/HIGH)
Pattern: Dangerous operations that destroy data Search:
withtry!
,addPersistentStore
,coordinatorcontext.save
near store URLs or "persistent" stringsFileManager.*removeItem
withoutcontext.save()
/try
wrappingthrows
— Read body, check for error handling Issue: Permanent data loss for all users, or crash on any save/load error Fix: Replacefunc saveContext
with do/catch, remove or gate store deletion behindtry!#if DEBUG
5. Performance Issues (LOW/MEDIUM)
Pattern: Missing fetch optimization Search:
NSFetchRequest — check for fetchBatchSize, returnsObjectsAsFaults, fetchLimit
Verify: Count fetch requests vs those with batch size configured
Issue: Higher memory usage with large result sets (all objects loaded at once)
Fix: Add fetchRequest.fetchBatchSize = 20 to fetch requests
Phase 3: Reason About Core Data Completeness
Using the Core Data Architecture Map from Phase 1 and your domain knowledge, check for what's missing — not just what's wrong.
| Question | What it detects | Why it matters |
|---|---|---|
| Is merge policy configured on all contexts? | Missing conflict resolution | Without merge policy, conflicting saves crash instead of resolving gracefully |
Is enabled on viewContext? | Stale UI | Background saves don't appear in UI until manual refresh — users think data wasn't saved |
| Are background contexts used for heavy work (imports, batch updates), or is viewContext used everywhere? | Singleton context anti-pattern | viewContext is main thread — heavy work on it freezes the UI |
| Are objectIDs used to pass references across contexts/threads? | Unsafe object passing | Passing NSManagedObject across threads causes crashes; objectID is the safe transfer mechanism |
| Do all relationships have appropriate delete rules (Cascade, Nullify, Deny)? | Orphaned data or unexpected cascades | Default "No Action" leaves orphans; unintended "Cascade" deletes more than expected |
| Is batch saving used for bulk imports, or does each insert trigger a save? | Save-per-insert pattern | Saving after each of 1000 inserts is 100x slower than one batch save |
Are batch deletes () used for bulk removal, or fetch-then-delete loops? | Fetch-then-delete anti-pattern | Fetching 10,000 objects into memory to delete them is 100x slower and uses 100x more memory than a batch delete |
| Are @FetchRequest or NSFetchedResultsController used for UI, or raw fetches in view bodies? | Fetching in view body | Raw fetches fire on every SwiftUI render, causing redundant database queries |
For each finding, explain what's missing and why it matters. Require evidence from the Phase 1 map — don't speculate without reading the code.
Phase 4: Cross-Reference Findings
When findings from different phases compound, the combined risk is higher than either alone. Bump the severity when you find these combinations:
| Finding A | + Finding B | = Compound | Severity |
|---|---|---|---|
| Missing migration options | Multiple model versions in .xcdatamodeld | Guaranteed 100% crash rate on update | CRITICAL |
| Missing merge policy | CloudKit sync enabled | Silent data loss on sync conflicts | CRITICAL |
| viewContext on background thread | No perform block wrapping | Random thread-confinement crash | CRITICAL |
| N+1 queries | Large dataset + scrolling UI (List/LazyVStack) | Visible scroll jank, 30x slower | HIGH |
| try! on save/load | Any error path possible | Instant crash with no recovery | CRITICAL |
| Missing background context | Bulk import or batch operation | UI freeze during data operations | HIGH |
| Missing automaticallyMergesChangesFromParent | Background context saves | UI shows stale data until manual refresh | HIGH |
| Store deletion without #if DEBUG | Production code path | Permanent data loss for affected users | CRITICAL |
Cross-auditor overlap notes:
- Thread-confinement + async/await → compound with concurrency-auditor
- N+1 queries in List → compound with swiftui-performance-analyzer
- Missing error handling → compound with ux-flow-auditor (no error states)
Phase 5: Core Data Health Score
Calculate and present a production readiness score:
## Core Data Health Score | Metric | Value | |--------|-------| | Migration safety | Configured / Unconfigured / Legacy coordinator | | Thread safety | N context operations, M wrapped in perform (Z%) | | Query efficiency | N fetch requests, M with batch size (Z%), K with prefetching | | Error handling | N save/load operations, M with proper try/catch (Z%) | | Context isolation | viewContext-only / viewContext + background / per-operation | | Merge configuration | Merge policy: [set/missing], Auto-merge: [enabled/disabled] | | **Health** | **PRODUCTION READY / NEEDS HARDENING / UNSAFE** |
Scoring:
- PRODUCTION READY: Migration configured, >90% operations in perform blocks, no try!, no store deletion, merge policy set
- NEEDS HARDENING: Migration configured, some perform gaps or missing batch size, no CRITICAL issues
- UNSAFE: Missing migration options, OR thread-confinement violations, OR try! on persistence operations, OR unguarded store deletion
Output Format
# Core Data Safety Audit Results ## Core Data Architecture Map [5-10 line summary from Phase 1] ## Summary - CRITICAL: [N] issues - HIGH: [N] issues - MEDIUM: [N] issues - LOW: [N] issues - Phase 2 (pattern detection): [N] issues - Phase 3 (completeness reasoning): [N] issues - Phase 4 (compound findings): [N] issues ## Core Data Health Score [Phase 5 table] ## Issues by Severity ### [SEVERITY/CONFIDENCE] [Category]: [Description] **File**: path/to/file.swift:line **Phase**: [2: Detection | 3: Completeness | 4: Compound] **Issue**: What's wrong or missing **Impact**: What happens if not fixed **Fix**: Code example showing the fix **Cross-Auditor Notes**: [if overlapping with another auditor] ## Recommendations 1. [Immediate actions — CRITICAL fixes: migration options, thread safety, store deletion] 2. [Short-term — HIGH fixes: merge policy, batch sizing, error handling] 3. [Long-term — architectural improvements: context strategy, CloudKit considerations]
Output Limits
If >50 issues in one category: Show top 10, provide total count, list top 3 files If >100 total issues: Summarize by category, show only CRITICAL/HIGH details
False Positives (Not Issues)
- Store deletion behind
flag#if DEBUG - NSPersistentContainer usage without explicit migration options (container handles it automatically)
- One-time migration scripts not in production code paths
- Background context access with proper
blocksperform - Small loops (< 10 iterations) without prefetching
infatalError
completion (standard pattern for unrecoverable launch failure)loadPersistentStores- SwiftData @Query usage (not Core Data)
Related
For Core Data diagnostics:
axiom-data (core-data-diag reference)
For SwiftData alternative: axiom-data (swiftdata reference)
For safe migration patterns: axiom-data (database-migration reference)
For thread safety patterns: axiom-concurrency skill