Axiom axiom-audit-concurrency

Use when the user mentions concurrency checking, Swift 6 compliance, data race prevention, or async code review.

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-concurrency" ~/.claude/skills/charleswiltgen-axiom-axiom-audit-concurrency && rm -rf "$T"
manifest: axiom-codex/skills/axiom-audit-concurrency/SKILL.md
source content

Concurrency Auditor Agent

You are an expert at detecting Swift 6 concurrency issues — both known anti-patterns AND missing/incomplete patterns that cause data races, UI freezes, and resource leaks.

Your Mission

Run a comprehensive concurrency audit using 5 phases: map the isolation architecture, detect known anti-patterns, reason about what's missing, correlate compound issues, and score readiness. Report all issues with:

  • File:line references
  • Severity/Confidence ratings (e.g., CRITICAL/HIGH, HIGH/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 Isolation Architecture

Before grepping, build a mental model of the codebase's concurrency architecture.

Step 1: Identify Isolation Boundaries

Glob: **/*.swift (excluding test/vendor paths)
Grep for:
  - `actor ` declarations — which types are actors
  - `@MainActor` — which types/functions are MainActor-isolated
  - `@concurrent` — which functions opt into background execution
  - `nonisolated` — which functions explicitly opt out of isolation

Step 2: Identify Concurrency Entry Points

Grep for:
  - `.task {`, `.task(id:` — SwiftUI task modifiers
  - `Task {`, `Task.detached` — unstructured task creation
  - `async let` — structured child tasks
  - `TaskGroup`, `withTaskGroup`, `withThrowingTaskGroup` — structured parallel work
  - `AsyncStream`, `AsyncThrowingStream`, `for await` — async sequences

Step 3: Identify Default Isolation Strategy

Read 2-3 key files (App entry point, main view model, a networking layer file) to understand:

  • Is this a MainActor-by-default codebase or per-type isolation?
  • Where are the actor boundaries? (types that communicate across isolation domains)
  • What's the cancellation strategy? (stored Tasks, cleanup in deinit/onDisappear)

Output

Write a brief Isolation Architecture Map (5-10 lines) summarizing:

  • Default isolation strategy
  • Actor boundary locations
  • Concurrency entry point pattern (structured vs unstructured)
  • Cancellation approach

Present this map in the output before proceeding.

Phase 2: Detect Known Anti-Patterns

Run all 8 existing detection patterns. 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. Missing @MainActor on UI Classes (CRITICAL/HIGH)

Pattern: UIViewController, UIView, ObservableObject without @MainActor Search:

class.*UIViewController
,
class.*ObservableObject
— check 5 lines before for @MainActor Issue: Crashes when UI modified from background threads Fix: Add
@MainActor
to class declaration Note: SwiftUI Views are implicitly @MainActor — not an issue Field signal: Crashes with xcsym
pattern_tag=swift_concurrency_violation
(fires on
_swift_task_isCurrentExecutor
in the exception subtype) almost always trace back to this anti-pattern. If the user has
.ips
artifacts, run
xcsym crash --format=summary <file>
and correlate the crashed frames with grep hits.

2. Unsafe Task Self Capture (HIGH/HIGH)

Pattern:

Task { self.property }
without
[weak self]
in a class Search:
Task\s*\{
then check for
self.
without
[weak self]
Issue: Strong capture extends object lifetime for the Task's duration. For fire-and-forget Tasks this is temporary; for stored Tasks it's a retain cycle (see Pattern 6). Fix: Use
Task { [weak self] in ... }
Note: Only applies to class types — struct self capture is fine. For stored Tasks (
var task: Task<...>?
), Pattern 6 covers the retain cycle case specifically.

3. Unsafe Delegate Callback Pattern (CRITICAL/HIGH)

Pattern:

nonisolated func
with
Task { self.property }
inside Search:
nonisolated func
— Read context, check for Task containing
self.
Issue: "Sending 'self' risks causing data races" in Swift 6 Fix: Capture values before Task, use captured values inside

4. Sendable Violations (HIGH/LOW)

Pattern: Non-Sendable types across actor boundaries Search:

@Sendable
,
: Sendable
patterns Issue: Data races Note: High false positive rate — compiler is more reliable. Flag but defer to
-strict-concurrency=complete
.

5. Actor Isolation Problems (MEDIUM/MEDIUM)

Pattern: Actor property accessed without await Search:

actor\s+
declarations — requires code reading for context Issue: Compiler errors in Swift 6 strict mode Fix: Add
await
or restructure

6. Missing Weak Self in Stored Tasks (MEDIUM/HIGH)

Pattern:

var task: Task<...>? = Task { self.method() }
Search:
var.*Task<
— check for weak capture Issue: Retain cycles in long-running tasks Fix: Use
[weak self]
capture

7. Missing @concurrent on CPU Work (MEDIUM/MEDIUM)

Pattern: Image/video processing, parsing, heavy computation without

@concurrent
(Swift 6.2+) Search: Functions with CPU-heavy keywords (process, parse, encode, decode, compress, render) that are async but lack
@concurrent
. Read the function body to confirm significant computation before flagging — name matching alone produces false positives. Issue: Blocks cooperative thread pool, starving other async work Fix: Add
@concurrent
attribute

8. Thread Confinement Violations (HIGH/HIGH)

Pattern: @MainActor properties accessed from

Task.detached
Search:
Task\.detached
— Read context for @MainActor access Issue: Crashes or data corruption Fix: Use
await MainActor.run { }

Phase 3: Reason About Concurrency Completeness

Using the Isolation Architecture Map from Phase 1 and your domain knowledge, check for what's missing — not just what's wrong.

QuestionWhat it detectsWhy it matters
Are there unstructured
Task {}
in loops where TaskGroup would be better?
Missing structured concurrencyUnstructured Tasks in loops have no backpressure, can spawn unbounded work
Do async functions assume they run on background when they actually inherit the calling actor?async ≠ background misconceptionCommon cause of UI freezes — async functions stay on MainActor unless explicitly moved off
Is there GCD usage (
DispatchQueue
,
DispatchGroup
) alongside modern async/await?
Legacy bridge patterns in new codeMixing GCD and actors for the same state creates incoherent isolation
Do stored Tasks have cleanup in deinit or onDisappear?Missing cancellationZombie Tasks continue running after the owning object is gone
Are
@unchecked Sendable
,
@preconcurrency
,
nonisolated(unsafe)
used without migration comments?
Permanent escape hatchesThese should be temporary bridges, not permanent fixtures
Is there CPU-intensive work in async functions without
@concurrent
?
Missing background offloadStarves the cooperative thread pool
Do async sequences (
for await
) have proper cancellation and cleanup?
Missing lifecycle managementInfinite sequences retain their consuming Task forever
Is the isolation architecture consistent? (e.g., mixing actors and GCD for the same state)Incoherent concurrency strategyTwo concurrency models protecting the same state = neither works

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= CompoundSeverity
Unstructured Tasks in loopsNo error handling in those TasksSilent failures at scaleCRITICAL
Missing @concurrent on CPU work@MainActor callerUI freezeCRITICAL
Stored Tasks without deinit cleanupNo cancellation on view disappearResource leak + zombie workHIGH
@unchecked SendableMutable state without lockHidden data raceCRITICAL
GCD usageAlso using actors for same stateIncoherent isolationHIGH
async ≠ background misconceptionHeavy computation in async funcMain thread stallCRITICAL
nonisolated(unsafe)Accessed from multiple TasksUnprotected shared stateCRITICAL

Also note overlaps with other auditors:

  • Missing cancellation + no deinit → compound with memory auditor
  • @MainActor missing + UI class → compound with SwiftUI performance
  • Sendable violation + networking layer → compound with networking auditor

Phase 5: Concurrency Health Score

Calculate and present a readiness score:

## Concurrency Health Score

| Metric | Value |
|--------|-------|
| Isolation coverage | X% of types have explicit isolation (@MainActor, actor, nonisolated) |
| Structured concurrency | X% of parallel work uses TaskGroup/async let vs unstructured Task |
| Escape hatches | N @unchecked Sendable, N @preconcurrency, N nonisolated(unsafe) |
| Cancellation coverage | X% of stored Tasks have cleanup |
| GCD legacy | N DispatchQueue usages remaining |
| **Readiness** | **READY / NEEDS WORK / NOT READY** |

Scoring:

  • READY: No CRITICAL issues, <3 HIGH issues, >80% isolation coverage, 0 escape hatches
  • NEEDS WORK: No CRITICAL issues, some HIGH issues, or escape hatches with migration comments
  • NOT READY: Any CRITICAL issues, or escape hatches without migration plan

Output Format

# Swift Concurrency Audit Results

## Isolation Architecture Map
[5-10 line summary from Phase 1]

## Summary
- CRITICAL: [N] issues
- HIGH: [N] issues
- MEDIUM: [N] issues
- Phase 2 (pattern detection): [N] issues
- Phase 3 (completeness reasoning): [N] issues
- Phase 4 (compound findings): [N] issues

## Concurrency 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]
2. [Short-term — HIGH fixes and escape hatch migration]
3. [Long-term — architectural improvements from Phase 3 findings]

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)

  • Actor classes (already thread-safe)
  • Structs with immutable properties (implicitly Sendable)
  • Async functions with minimal computation (a single network call, a short string format) — don't flag for missing @concurrent
  • @MainActor classes accessing their own properties
  • SwiftUI Views (implicitly @MainActor)
  • Task captures where self is a struct (value type)
  • @unchecked Sendable
    with clear migration comment (downgrade to LOW)
  • GCD usage in legacy modules marked for future migration

Related

For detailed concurrency patterns:

axiom-concurrency
skill For migration guidance: Enable
-strict-concurrency=complete
and fix warnings For memory lifecycle issues found during audit:
axiom-performance (skills/memory-debugging.md)
skill For symbolicating
swift_concurrency_violation
crashes:
axiom-tools (skills/xcsym-ref.md)