$npx -y skills add AvdLee/Swift-Concurrency-Agent-Skill --skill swift-concurrencyDiagnose Swift Concurrency issues, refactor callback-based code to async/await, and guide Swift 6 migration when working with tasks, actors, @MainActor, Sendable, data races, thread safety, or concurrency-related compiler and linter warnings.
| 1 | # Swift Concurrency |
| 2 | |
| 3 | ## Fast Path |
| 4 | |
| 5 | Before proposing a fix: |
| 6 | |
| 7 | 1. Analyze `Package.swift` or `.pbxproj` to determine Swift language mode, strict concurrency level, default isolation, and upcoming features. Do this always, not only for migration work. |
| 8 | 2. Capture the exact diagnostic and offending symbol. |
| 9 | 3. Determine the isolation boundary: `@MainActor`, custom actor, actor instance isolation, or `nonisolated`. |
| 10 | 4. Confirm whether the code is UI-bound or intended to run off the main actor. When spawning unstructured tasks, inspect the synchronous prefix (everything before the first `await`): start on `@MainActor` only when that prefix truly needs main-actor access; otherwise use `Task { @concurrent in ... }` and hop back with `MainActor.run` only after the suspension. A trivial non-main line (for example, `print`) followed by main-actor work in the same prefix is not a reason to use `@concurrent`. For delayed retries, timers, and backoff tasks, separate the waiting from the UI mutation. The sleep often belongs off the main actor even when the final state update belongs on it. |
| 11 | |
| 12 | Project settings that change concurrency behavior: |
| 13 | |
| 14 | | Setting | SwiftPM (`Package.swift`) | Xcode (`.pbxproj`) | |
| 15 | |---|---|---| |
| 16 | | Language mode | `swiftLanguageVersions` or `-swift-version` (`// swift-tools-version:` is not a reliable proxy) | Swift Language Version | |
| 17 | | Strict concurrency | `.enableExperimentalFeature("StrictConcurrency=targeted")` | `SWIFT_STRICT_CONCURRENCY` | |
| 18 | | Default isolation | `.defaultIsolation(MainActor.self)` | `SWIFT_DEFAULT_ACTOR_ISOLATION` | |
| 19 | | Upcoming features | `.enableUpcomingFeature("NonisolatedNonsendingByDefault")` | `SWIFT_UPCOMING_FEATURE_*` | |
| 20 | | Approachable Concurrency | N/A (use individual upcoming features) | `SWIFT_APPROACHABLE_CONCURRENCY` | |
| 21 | |
| 22 | > **Xcode 26 note**: New projects created in Xcode 26 will often start with `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor` and `SWIFT_APPROACHABLE_CONCURRENCY = YES` enabled by default. Treat these as likely defaults for newly created projects, not as confirmed settings. |
| 23 | |
| 24 | If any of these are unknown, ask the developer to confirm them before giving migration-sensitive guidance. Do not guess, even for new Xcode 26 projects. |
| 25 | |
| 26 | Guardrails: |
| 27 | |
| 28 | - Do not recommend `@MainActor` as a blanket fix. Justify why the code is truly UI-bound. |
| 29 | - Prefer structured concurrency over unstructured tasks. Use `Task.detached` only with a clear reason. |
| 30 | - If recommending `@preconcurrency`, `@unchecked Sendable`, or `nonisolated(unsafe)`, require a documented safety invariant and a follow-up removal plan. |
| 31 | - Optimize for the smallest safe change. Do not refactor unrelated architecture during migration. |
| 32 | - Course references are for deeper learning only. Use them sparingly and only when they clearly help answer the developer's question. |
| 33 | |
| 34 | ## Quick Fix Mode |
| 35 | |
| 36 | Use Quick Fix Mode when all of these are true: |
| 37 | |
| 38 | - The issue is localized to one file or one type. |
| 39 | - The isolation boundary is clear. |
| 40 | - The fix can be explained in 1-2 behavior-preserving steps. |
| 41 | |
| 42 | Skip Quick Fix Mode when any of these are true: |
| 43 | |
| 44 | - Build settings or default isolation are unknown. |
| 45 | - The issue crosses module boundaries or changes public API behavior. |
| 46 | - The likely fix depends on unsafe escape hatches. |
| 47 | |
| 48 | ## Common Diagnostics |
| 49 | |
| 50 | | Diagnostic | First check | Smallest safe fix | Escalate to | |
| 51 | |---|---|---|---| |
| 52 | | `Main actor-isolated ... cannot be used from a nonisolated context` | Is this truly UI-bound? | Isolate the caller to `@MainActor` or use `await MainActor.run { ... }` only when main-actor ownership is correct. | `references/actors.md`, `references/threading.md` | |
| 53 | | `Actor-isolated type does not conform to protocol` | Must the requirement run on the actor? | Prefer isolated conformance (e.g., `extension Foo: @MainActor SomeProtocol`); use `nonisolated` only for truly nonisolated requirements. | `references/actors.md` | |
| 54 | | `Sending value of non-Sendable type ... risks causing data races` | What isolation boundary is being crossed? | Keep access inside one actor, or convert the transferred value to an immutable/value type. | `references/sendable.md`, `references/threading.md` | |
| 55 | | `SwiftLint async_without_await` | Is `async` actually required by protocol, override, or `@concurrent`? | Remove `async`, or use a narrow suppression with rationale. Never add fake awaits. | `references/linting.md` | |
| 56 | | `wait(...) is unavailable from asynchronous contexts` | Is this legacy XCTest async waiting? | Replace with `await fulfillment(of:)` or Swift Testing equivalents. | `references/testing.md` | |
| 57 | | Core Data concurrency warnings | Are `NSManagedObject` instances crossing contexts or actors? | Pass `NSManagedObjectID` or map to a Sendable value type. | `references/cor |