swift-concurrency-review
Use when reviewing Swift 5.5+ code containing `await`, `actor`, `Task { }`, `@MainActor`, `@unchecked Sendable`, cancellable timer/deadline handles, or…
Skill by stuartshields
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/stuartshields-swift-concurrency-review
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: testing, productivity
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/swift-concurrency-review
What's inside
- Skill name
swift-concurrency-review- Description
- Use when reviewing Swift 5.5+ code containing `await`, `actor`, `Task { }`, `@MainActor`, `@unchecked Sendable`, cancellable timer/deadline handles, or long-lived `for await` AsyncStream consumers — especially diffs extending existing actors with new methods. Catches post-`await` state-overwrite races, TOCTOU around Task spawn, cancellation-not-honoured, terminal-state clobber, and consumer-Task-ownership bugs that hang Swift Testing at exit.
- License
- MIT
- Instructions
- 150 lines in SKILL.md · view on GitHub
Files (1)
- SKILL.md
SKILL.md (first lines)
<!-- Last updated: 2026-05-10T20:55+10:00 -->
# Swift Concurrency Review
## Overview
Swift 6 strict concurrency catches data races at compile time but lets a class of semantic bugs through: state-overwrite races, TOCTOU around `Task { }`, and cancellation that doesn't actually prevent firing. This is a reviewer's checklist for those bug classes. Apply on any diff touching `actor`, `@MainActor`, `await`, `Task {`, lock-protected state, or deadline timers.
## The Five Questions
### 1. After every `await`, is **state** re-checked — not just the index?
When an actor method suspends at `await`, any other method on the actor can run to completion before it resumes. Re-finding a record by id ≠ verifying its state is still what you expected.
```swift
// BAD
try await transport.send(message)
guard let idx = outbound.firstIndex(where: { $0.id == id }) else { return }
outbound[idx].direction = .delivered // overwrites .cancelled / .deadlineMissed
// GOOD
try await transport.send(message)
guard let idx = outbound.firstIndex(where: { $0.id == id }),
outbound[idx].direction == .sending else { return }
outbound[idx].direction = .delivered
```
**Invariant: terminal states are sticky.** Once a record is `.cancelled` / `.expired` / `.deadlineMissed`, no later success path may overwrite it. The guard encodes this invariant.
### 2. Is `Task { }` body reading mutable state, or is it snapshotted at spawn?
A `Task { }` body runs later, on some executor. Reading `clock.now`, `self.count`, or any mutable state *inside* the closure is a TOCTOU — the value is read at an arbitrary later moment, not at spawn.
```swift
// BAD — clock.now read inside the Task
task = Task { [weak self] in
let seconds = max(0, deadline.timeIntervalSince(clock.now))
try? await clock.sleep(for: seconds)
}
// GOOD — clock.now read before Task exists, captured as constant
let seconds = max(0, deadline.timeIntervalSince(clock.now))
task = Task { [weak self] in
try? await clock.sleep(for: seconds)
}
```
### 3. If cancellable, does `cancel()` actually prevent firing?
`Task.cancel()` signals cancellation; it does not stop a running task. `try? await clock.sleep(for:)` swallows `CancellationError` and falls through. A handle that looks cancellable can still fire after `cancel()`.
**Fix options** (at least one must exist and have a test):
- Flip a `fired: Bool` / `cancelled: Bool` under the lock in `cancel()`, check it after sleep
- Check `Task.isCancelled` explicitly after the sleep
- Use `try await` (not `try?`) and let `CancellationError` propagate
### 4. Does fire-and-forget `Task { }` manage the lifetime of what it captures?
A detached `Task { }` that captures `self` strongly or `any SomeProtocol` and runs after the owner tears down is a leak or a crash. Use `[weak self]` + early return, or await on a parent task that's guaranteed to complete.
### 5. Is a long-lived `for await` consumer Task owned by something whose `deinit` finishes the source?
A `Task { for await event in stream { ... } }` stored on an actor does NOT die deterministically when the actor goes out of scope. `[weak self]` only nils the reference *inside* the loop body — not while the task is suspended at `for await`, which is most of the task's life. AsyncStream's auto-finish-on-continuation-dealloc is non-deterministic, and Swift Testing waits for all background Tasks before exiting — symptom: `swift test` prints all "passed" lines, then **hangs at exit** before the run summary.
The fix is structural: park the Task on a non-actor class that the actor strong-references. When the actor deinits, the class deinits, and the class's `deinit` runs synchronously on the deallocating thread — finishing the stream (so the `for await` returns nil) and cancelling the Task (belt-and-suspenders for a body that's mid-event).
```swift
// BAD — Task stored on actor, not deterministically torn down
actor MyActor {
private var consumerTask: Task<Void, Never>?
init(stream: AsyncStream<RawEvent>) {
consumerTask = Task { [weak self] in
for await event in stream {
guard let self else { return }
await self.process(event)
}
}
}
}
// GOOD — Task owned by a non-actor class with synchronous deinit
final class ConsumerBox: @unchecked Sendable {
let stream: AsyncStream<RawEvent>
private let cont: AsyncStream<RawEvent>.Continuation
private var consumerTask: Task<Void, Never>?
init() {
var captured: AsyncStream<RawEvent>.Continuation!
self.stream = AsyncStream { captured = $0 }
self.cont = captured
}
deinit {
cont.finish() // 1. wake the consumer, return nil from `for await`
consumerTask?.cancel() // 2. belt-and-suspenders for mid-await body
}
func bind(to actor: MyActor) {
let s = stream
consumerTask = Task { [weak actor] in
for await event in s {
guard let actor else { return }
await actor.process(event)
}
}
}
}
actor MyActor {
private let box = ConsumerBox()
init() { box.bind(to: self) }
}
```
The non-actor class wrapper is load-bearing — actor `deinit` runs in a separate isolation context and doesn't synchronously tear down the Task chain.
## Review Checklist
Flag any violation as at least a Major:
- [ ] Every `await` is followed by a state re-check before mutating — not just a re-find by id
- [ ] Terminal-state guards are **mirrored** across methods that can transition the same record (if `send` guards against `.cancelled`, `cancel` must also guard against `.delivered`)
- [ ] Values used inside `Task { }` closures are captured at spawn time, not read inside
- [ ] Every `cancel()` / `teardown()` has a test proving the handle does not fire after cancellation
- [ ] `try?` around `await` is deliberate and justified — not a paper over a cancellation bugFrom stuartshields/claude-setup, MIT.
More from stuartshields/claude-setup
- 0.507score
module-depth-review
stuartshields
Use when reviewing whether module boundaries earn their keep — finding shallow modules that should be consolidated into deep ones.
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
debugging
stuartshields
Structured debugging protocol.
SkillCFreshness: C★ 2Claude Code +9 - 0.504score
clean-worktrees
stuartshields
Remove stale agent worktree directories and their orphaned branches.
SkillCFreshness: C★ 2Claude Code +9 - 0.504score
block-journey
stuartshields
Discover all files for a block or component, trace the editorial and front-end user journeys, and write a journey document to docs/journeys/.
SkillCFreshness: C★ 2Claude Code +9 - 0.502score
tool-usage
stuartshields
Recovery protocol for tool failures and search budgets.
SkillCFreshness: C★ 2Claude Code +9 - 0.482score
audit-vs-fix-discipline
stuartshields
Use when the user asks to audit, review, investigate, check, scan, "look at", or "find issues in" code — anything that sounds like a diagnostic request…
SkillCFreshness: C★ 2Claude Code +9 - 0.440score
vibe-user
stuartshields
Open an app in Playwright and explore it as a real user with no prior knowledge.
SkillDFreshness: D★ 2Claude Code +9 - 0.440score
debug-rules
stuartshields
Diagnose which rules loaded, which didn't, and why.
SkillDFreshness: D★ 2Claude Code +9 - 0.440score
figma
stuartshields
Figma-to-code workflow using MCP tools.
SkillDFreshness: D★ 2Claude Code +9 - 0.440score
playwright
stuartshields
Playwright MCP browser automation workflow.
SkillDFreshness: D★ 2Claude Code +9 - 0.440score
qa-check
stuartshields
Auto-detects the project's tech stack, then audits for Accessibility, Performance, and Code Quality.
SkillDFreshness: D★ 2Claude Code +9 - 0.440score
review-memory
stuartshields
Review auto-memory for the current project.
SkillDFreshness: D★ 2Claude Code +9
Signals
- Stars
- 2
- Forks
- 0
- Open issues
- 0
- Last commit (this folder)
- May 18, 2026 (5 months ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/stuartshields/claude-setup
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from stuartshields
- 0.507score
module-depth-review
stuartshields
Use when reviewing whether module boundaries earn their keep — finding shallow modules that should be consolidated into deep ones.
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
debugging
stuartshields
Structured debugging protocol.
SkillCFreshness: C★ 2Claude Code +9 - 0.504score
clean-worktrees
stuartshields
Remove stale agent worktree directories and their orphaned branches.
SkillCFreshness: C★ 2Claude Code +9 - 0.504score
block-journey
stuartshields
Discover all files for a block or component, trace the editorial and front-end user journeys, and write a journey document to docs/journeys/.
SkillCFreshness: C★ 2Claude Code +9 - 0.502score
tool-usage
stuartshields
Recovery protocol for tool failures and search budgets.
SkillCFreshness: C★ 2Claude Code +9 - 0.482score
audit-vs-fix-discipline
stuartshields
Use when the user asks to audit, review, investigate, check, scan, "look at", or "find issues in" code — anything that sounds like a diagnostic request…
SkillCFreshness: C★ 2Claude Code +9
Cite this page
swift-concurrency-review skill. Agent Mart. https://agentmart.dev/skills/stuartshields-swift-concurrency-review (last updated 2026-10-24)