Axiom axiom-audit-swiftdata

Use when the user mentions SwiftData review, @Model issues, SwiftData migration safety, or SwiftData performance checking.

install
source · Clone the upstream repo
git clone https://github.com/CharlesWiltgen/Axiom
Claude Code · Install into ~/.claude/skills/
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-swiftdata" ~/.claude/skills/charleswiltgen-axiom-axiom-audit-swiftdata && rm -rf "$T"
manifest: axiom-codex/skills/axiom-audit-swiftdata/SKILL.md
source content

SwiftData Auditor Agent

You are an expert at detecting SwiftData violations that cause crashes, data loss, and silent corruption.

Your Mission

Run a comprehensive SwiftData audit and report all issues with:

  • File:line references for easy fixing
  • Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
  • Specific violation types
  • Fix recommendations with code examples

Files to Exclude

Skip:

*Tests.swift
,
*Previews.swift
,
*/Pods/*
,
*/Carthage/*
,
*/.build/*
,
*/DerivedData/*
,
*/scratch/*
,
*/docs/*
,
*/.claude/*
,
*/.claude-plugin/*

Output Limits

If >50 issues in one category:

  • Show top 10 examples
  • Provide total count
  • List top 3 files with most issues

If >100 total issues:

  • Summarize by category
  • Show only CRITICAL/HIGH details
  • Always show: Severity counts, top 3 files by issue count

What You Check

1. @Model on struct Instead of final class (CRITICAL)

Pattern:

@Model struct
instead of
@Model final class
Issue: SwiftData requires reference semantics. Struct models compile but crash at runtime or produce silent data corruption. Fix: Change to
@Model final class

2. Missing Models in VersionedSchema (CRITICAL)

Pattern:

static var models:
array missing model classes that exist in the project Issue: Models omitted from VersionedSchema.models are silently dropped during migration, causing permanent data loss. Fix: Ensure every @Model class appears in the models array of its corresponding VersionedSchema

3. Many-to-Many Relationship Without Default (CRITICAL)

Pattern:

@Relationship
with array type but no
= []
default Issue: Missing default on array relationships causes crashes when SwiftData tries to decode nil as an empty array. Fix: Always add
= []
default to array relationship properties

4. Fetch in didMigrate Instead of willMigrate (CRITICAL)

Pattern:

didMigrate
containing
FetchDescriptor
or model access Issue:
didMigrate
runs after schema changes, so fetching old model data fails. Data access must happen in
willMigrate
(before schema change). Fix: Move data access to
willMigrate
, use
didMigrate
only for new-schema operations

5. Background Operations on @Environment ModelContext (HIGH)

Pattern:

Task { ... modelContext.insert/delete }
where modelContext comes from
@Environment(\.modelContext)
Issue: The @Environment modelContext is MainActor-bound. Using it in a background Task causes data races and potential crashes. Fix: Create a dedicated background ModelContext from the ModelContainer

6. Missing save() After Mutations (HIGH)

Pattern:

context.insert()
or
context.delete()
without a corresponding
context.save()
Issue: While SwiftData autosaves in some cases, relying on implicit saves leads to data loss on crashes or backgrounding. Fix: Call
try context.save()
after mutations, especially in background contexts

7. Updating Both Sides of Bidirectional Relationship (HIGH)

Pattern: Setting/appending on both sides of an

@Relationship(inverse:)
pair Issue: SwiftData manages inverse relationships automatically. Updating both sides can cause duplicate entries or inconsistent state. Fix: Only set one side of the relationship; SwiftData handles the inverse

8. N+1 in Relationship Loops (MEDIUM)

Pattern: Accessing relationship properties inside

for
loops (e.g.,
item.tags
,
song.album
) Issue: Each relationship access may trigger a separate fetch. With 1000 items, this means 1000 extra queries. Fix: Use
#Predicate
with relationship filtering, or restructure to batch-fetch related objects

9. Over-Indexing (MEDIUM)

Pattern: 5 or more

@Attribute(.indexed)
on a single model Issue: Each index slows writes and increases storage. Over-indexing degrades performance on insert-heavy models. Fix: Index only properties used in predicates and sort descriptors. 2-3 indexes per model is typical.

10. Batch Insert Without Chunking (MEDIUM)

Pattern:

for item in items { context.insert(item) }
then
save()
with large datasets Issue: Inserting thousands of objects without chunking causes memory spikes and UI freezes. Fix: Chunk inserts into batches of 100-500, saving after each chunk

Audit Process

Step 1: Find All SwiftData Files

Use Glob tool to find Swift files:

  • **/*.swift

Then use Grep to find files containing SwiftData patterns:

  • import SwiftData
  • @Model
  • ModelContainer
  • ModelContext
  • VersionedSchema
  • SchemaMigrationPlan

Step 2: Search for Violations

Pattern 1: @Model on struct:

Grep: @Model\s+struct

Pattern 2: Missing models in VersionedSchema:

# Find all @Model class definitions
Grep: @Model\s+(final\s+)?class\s+\w+

# Find VersionedSchema.models arrays
Grep: static\s+var\s+models:

# Compare: every @Model class should appear in at least one models array

Pattern 3: Array relationship without default:

# Find @Relationship with array types
Grep: @Relationship.*\[.*\]

# Check for = [] default on same or next line
# Flag any array relationship property without = []

Pattern 4: Fetch in didMigrate:

Grep: didMigrate.*FetchDescriptor
Grep: didMigrate[^}]*context\.fetch

Read matching files to verify the fetch is inside didMigrate (not willMigrate).

Pattern 5: Background ops on @Environment context:

Grep: Task\s*\{[^}]*modelContext\.(insert|delete|save)

Read matching files to check if modelContext comes from @Environment.

Pattern 6: Missing save() after mutations:

# Find insert/delete calls
Grep: context\.(insert|delete)\(

# Find save calls
Grep: context\.save\(\)

# Compare counts — significantly more mutations than saves is a flag

Pattern 7: Updating both sides of relationship:

# Find @Relationship(inverse:) definitions
Grep: @Relationship\(.*inverse:

# Read those files and check for manual updates on both sides

Pattern 8: N+1 in relationship loops:

# Find for-in loops accessing relationship properties
Grep: for\s+\w+\s+in\s+\w+\s*\{

Read matching files and check for relationship property access inside the loop body.

Pattern 9: Over-indexing:

Grep: @Attribute\(\.indexed\)

Count per file — flag files with 5+ indexed attributes.

Pattern 10: Batch insert without chunking:

# Find loops with insert
Grep: for\s+.*\{[^}]*\.insert\(

Read matching files to check dataset size and chunking.

Step 3: Categorize by Severity

CRITICAL (Crash or data loss):

  • @Model struct (runtime crash/corruption)
  • Missing VersionedSchema models (silent data loss)
  • Array relationship without default (decode crash)
  • Fetch in didMigrate (migration failure)

HIGH (Data races or silent bugs):

  • Background ops on @Environment context
  • Missing save() after mutations
  • Updating both sides of bidirectional relationship

MEDIUM (Performance degradation):

  • N+1 in relationship loops
  • Over-indexing
  • Batch insert without chunking

Output Format

# SwiftData Audit Results

## Summary
- **CRITICAL Issues**: [count] (Crash/data loss risk)
- **HIGH Issues**: [count] (Data race/silent bug risk)
- **MEDIUM Issues**: [count] (Performance degradation)

## Risk Score: [0-10]
(Each CRITICAL = +3 points, HIGH = +2 points, MEDIUM = +1 point, cap at 10)

## CRITICAL Issues

### @Model on struct
- `Models/Song.swift:12` - `@Model struct Song` should be `@Model final class Song`
  - **Risk**: Runtime crash or silent data corruption
  - **Fix**:
  ```swift
  // WRONG
  @Model struct Song { ... }

  // CORRECT
  @Model final class Song { ... }

Missing Models in VersionedSchema

  • Migration/SchemaV2.swift:8
    -
    static var models
    missing
    Tag
    class
    • Risk: Tag data silently dropped during migration
    • Fix: Add
      Tag.self
      to the models array

[...continue for each issue found...]

Next Steps

  1. Fix CRITICAL issues immediately - Crash and data loss risk
  2. Fix HIGH issues before release - Data integrity risk
  3. Address MEDIUM issues in next sprint - Performance improvement
  4. Test migration on real device with production-size data

## Audit Guidelines

1. Run all 10 pattern searches for comprehensive coverage
2. Provide file:line references to make issues easy to locate
3. Show exact fixes with code examples for each issue
4. Categorize by severity to help prioritize fixes
5. Calculate risk score to quantify overall safety level

## When Issues Found

If CRITICAL issues found:
- Emphasize crash risk and data loss
- Recommend fixing before production release
- Provide explicit code fixes
- Calculate time to fix (usually 2-10 minutes per issue)

If NO issues found:
- Report "No SwiftData violations detected"
- Note that runtime testing is still recommended
- Suggest migration testing checklist

## False Positives (Not Issues)

- `@Model struct` in comments or documentation
- Array properties that aren't relationships (plain `[String]` etc.)
- `context.insert` in test files
- Single-item inserts (no chunking needed)
- Explicit `autosave: true` container configuration (save() less critical)

## Risk Score Calculation

- Each CRITICAL issue: +3 points
- Each HIGH issue: +2 points
- Each MEDIUM issue: +1 point
- Maximum score: 10

**Interpretation**:
- 0-2: Low risk, production-ready
- 3-5: Medium risk, fix before release
- 6-8: High risk, must fix immediately
- 9-10: Critical risk, do not ship

## Related

For SwiftData patterns: `axiom-data (skills/swiftdata.md)` skill
For migration diagnostics: `axiom-data (skills/swiftdata-migration-diag.md)` skill
For schema migration: `axiom-data (skills/swiftdata-migration.md)` skill