Skip to content

[#690] Add Swift Concurrency Skill#691

Open
phongvhd93 wants to merge 2 commits intodevelopfrom
feature/#690-skill-for-swift6
Open

[#690] Add Swift Concurrency Skill#691
phongvhd93 wants to merge 2 commits intodevelopfrom
feature/#690-skill-for-swift6

Conversation

@phongvhd93
Copy link
Copy Markdown
Contributor

@phongvhd93 phongvhd93 commented Apr 15, 2026

What happened 👀

Defined a base skills set for swift concurrency, which covered the following areas

  • SKILL.md: behavior contract, canonical patterns, verification checklist
    • references/actors.md: actor isolation, nonisolated, global actors, Mutex vs Actor
    • references/sendability.md: Sendable, @unchecked Sendable, sending parameters
    • references/structured-concurrency.md: async let, TaskGroup, Task cancellation
    • references/async-sequences.md: AsyncStream, AsyncThrowingStream, buffering

Insight 📝

This adds a Swift 6 Concurrency AI agent skill to help Claude Code assist developers writing correct concurrent Swift code, only forward-looking patterns aligned with Swift 6's strict data-race-safety model.

  • Diagnose and fix data race compiler errors correctly — never suppressing them with nonisolated(unsafe) without justification
  • Choose the right primitive — actor for async shared state, @mainactor for UI, Mutex only for short synchronous critical sections
  • Apply structured concurrency (async let, TaskGroup) instead of ad-hoc Task { } chains
  • Handle AsyncStream continuations safely, ensuring .finish() is always called
    Propagate task cancellation correctly through long-running operations

Summary by CodeRabbit

  • Documentation
    • Added a Swift 6 strict-concurrency skill/workflow and companion reference guides covering actors and global/@mainactor guidance, structured concurrency patterns, async sequences, and Sendable rules.
    • Includes triage/routing guidance, canonical code patterns, do/don’t checklists, verification steps (cancellation, stream termination), and practical examples for authoring and reviewing concurrent Swift code.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

📝 Walkthrough

Walkthrough

Adds a Swift 6 strict-concurrency skill package: one skill definition and four new reference guides covering actors, async sequences, Sendable, and structured concurrency, including patterns, triage rules, and verification checklists.

Changes

Cohort / File(s) Summary
Skill Definition
skills/swift-concurrency/SKILL.md
New skill spec defining agent behavior, isolation strategies (actors, Mutex guidance, @MainActor), structured-concurrency norms, Sendable policies, triage rubric, canonical patterns, routing rules, and verification checklist.
Actors Reference
skills/swift-concurrency/references/actors.md
New reference on actor semantics, nonisolated/nonisolated(unsafe) guidance, global actors (@MainActor, custom global actors), actor protocol conformance, reentrancy examples, and Mutex vs actor decision guidance.
Async Sequences Reference
skills/swift-concurrency/references/async-sequences.md
New guide for producing/consuming AsyncSequence, AsyncStream/AsyncThrowingStream usage, buffering and onTermination patterns, ensuring finish()/finish(throwing:), cancellation-aware consumption, and Combine bridging.
Sendability Reference
skills/swift-concurrency/references/sendability.md
New doc describing Sendable rules in Swift 6 strict concurrency: implicit/explicit conformance, @unchecked Sendable and sending parameters, @Sendable closures, and refactor patterns.
Structured Concurrency Reference
skills/swift-concurrency/references/structured-concurrency.md
New guide on structured concurrency: async let, withTaskGroup variants, Task vs Task.detached, cancellation propagation and handling, @TaskLocal/priority behavior, and cancellation bridging examples.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • suho
  • patcharapon-j
  • minhnimble
  • vnntsu
  • Shayokh144
  • dquangit
  • roman-nimble
  • nhgia
  • ducbm051291
  • kevinhoangpq

Poem

🐰 I hopped through docs with nimble feet,
Actors, streams, and Sendable neat,
Tasks aligned and checks in view,
Safe concurrency — a carrot stew,
Cheers from this rabbit, light and fleet!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change as adding a Swift Concurrency skill, which matches the substantial new documentation and agent behavior contract introduced in the changeset.
Description check ✅ Passed The description follows the template structure with sections for what happened and insight, clearly detailing the skill components (SKILL.md and four reference documents) and the rationale behind the Swift 6 concurrency patterns.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#690-skill-for-swift6

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@skills/swift-concurrency/references/actors.md`:
- Around line 171-188: The markdown code block opened for the Swift examples
(showing DispatchQueue.main.async, await MainActor.run, and the `@MainActor` func
updateUI()) is missing a closing triple-backtick, which breaks the document
structure; fix it by adding a closing ``` immediately after the updateUI()
function example so the code fence is properly closed before the "### Custom
global actor" heading (locate the block containing DispatchQueue.main.async,
MainActor.run, and updateUI to insert the closing fence).
- Around line 21-31: The actor example assigns to a non-existent property
currentUserId in methods logIn and logOut while the declared property is userId;
update logIn and logOut to set userId (and leave or add any intended
isAuthenticated handling consistently) so the code compiles, and also add a
closing code fence after the block ending at line 187 to fix the broken Markdown
rendering.

In `@skills/swift-concurrency/references/async-sequences.md`:
- Around line 113-133: The AsyncThrowingStream builder closure is synchronous so
you must not call await directly; wrap any async work (e.g., calls to fetch) in
a child Task (or Task.detached) inside the AsyncThrowingStream initializer and
perform continuation.yield / continuation.finish(throwing:) from that Task;
similarly, move defer { continuation.finish() } into the Task or ensure every
async exit path calls continuation.finish()/finish(throwing:) from the Task
context to guarantee proper completion while avoiding await in the outer
(Continuation) -> Void closure.

In `@skills/swift-concurrency/references/structured-concurrency.md`:
- Around line 185-196: The onCancel closure in your withTaskCancellationHandler
cannot see the local request declared inside withCheckedThrowingContinuation;
move the request declaration to an outer scope (e.g., var request:
URLSessionDataTask? before calling withCheckedThrowingContinuation), assign the
task to that variable inside the continuation block, and then call
request?.cancel() in the onCancel handler so the legacy data task can be
cancelled safely; ensure continuation resumes only once and consider clearing
request after resume to avoid leaks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 380244de-a9ed-45d0-9bf2-b4ad041d3811

📥 Commits

Reviewing files that changed from the base of the PR and between 4cf2e99 and 95fb58c.

📒 Files selected for processing (5)
  • skills/swift-concurrency/SKILL.md
  • skills/swift-concurrency/references/actors.md
  • skills/swift-concurrency/references/async-sequences.md
  • skills/swift-concurrency/references/sendability.md
  • skills/swift-concurrency/references/structured-concurrency.md

Comment thread skills/swift-concurrency/references/actors.md
Comment thread skills/swift-concurrency/references/actors.md
Comment thread skills/swift-concurrency/references/async-sequences.md
Comment thread skills/swift-concurrency/references/structured-concurrency.md
@ducbm051291 ducbm051291 linked an issue Apr 15, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Contributor

@ducbm051291 ducbm051291 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check and resolve coderabittai comments

Comment thread skills/swift-concurrency/references/actors.md Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@skills/swift-concurrency/references/structured-concurrency.md`:
- Around line 185-197: The code mutates and reads the shared variable `request`
from both the operation passed to `withTaskCancellationHandler` and the
`onCancel` closure, which can run concurrently; to fix it make access to
`request` concurrency-safe by moving it into a synchronization boundary (for
example an actor or a simple lock/Mutex) and use that boundary from both
`withCheckedThrowingContinuation` (the operation that calls
`legacySession.dataTask`) and the `onCancel` closure so reads/writes to
`request` are serialized; update references to `request`,
`withTaskCancellationHandler`, `onCancel`, and `withCheckedThrowingContinuation`
accordingly so cancellation calls `cancel()` via the synchronized container.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: afdc34d0-ebcf-4d38-9c8f-9fca8eb4883b

📥 Commits

Reviewing files that changed from the base of the PR and between 95fb58c and 67dec10.

📒 Files selected for processing (3)
  • skills/swift-concurrency/references/actors.md
  • skills/swift-concurrency/references/async-sequences.md
  • skills/swift-concurrency/references/structured-concurrency.md

Comment on lines +185 to +197
var request: URLSessionDataTask?
try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
request = legacySession.dataTask(with: url) { data, _, error in
if let data { continuation.resume(returning: data) }
else { continuation.resume(throwing: error ?? URLError(.unknown)) }
}
request?.resume()
}
} onCancel: {
// Called synchronously on cancellation
request?.cancel() // Cancel the legacy task
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Swift 6 withTaskCancellationHandler captured var in operation and onCancel closure data race diagnostic

💡 Result:

In Swift 6's strict concurrency checking, using withTaskCancellationHandler(operation:onCancel:isolation:) can trigger data race diagnostics when the onCancel closure (which is @Sendable -> Void) captures mutable variables (var) from the surrounding scope. This is because the cancellation handler may execute concurrently with the operation closure, as explicitly noted in Apple's official documentation: "This onCancel closure might execute concurrently with the operation." The diagnostic is typically "Reference to captured var in concurrently-executing code" or a variant like "Capture of 'X' with non-sendable type in a @Sendable closure", enforced in Swift 6 language mode. Swift prevents mutable state from being shared unsafely across concurrency domains. To resolve: - Change captured var to let if immutable after capture. - Use explicit capture lists to shadow with let, e.g., onCancel: { [weak capturedVar] in ... }. - Ensure captured types conform to Sendable if shared. - Use actor isolation or restructure to avoid sharing mutable state. Official docs confirm the concurrent execution model (source 1). General diagnostics for captured vars in concurrent closures are documented across sources, including fixes via capture lists (sources 7,15,23). No specific open bug for this exact diagnostic in withTaskCancellationHandler, but it aligns with region-based isolation checks.

Citations:


🏁 Script executed:

# Verify the file exists and check the exact lines mentioned
file_path="skills/swift-concurrency/references/structured-concurrency.md"
if [ -f "$file_path" ]; then
  echo "=== File exists. Checking line range 185-197 ==="
  sed -n '180,200p' "$file_path" | cat -n
else
  echo "File not found at: $file_path"
  # Try to find similar files
  find . -name "*structured-concurrency*" -type f 2>/dev/null | head -20
fi

Repository: nimblehq/ios-templates

Length of output: 966


🏁 Script executed:

# Check broader context around the code section (more lines before and after)
file_path="skills/swift-concurrency/references/structured-concurrency.md"
echo "=== Context: Lines 170-210 ==="
sed -n '170,210p' "$file_path" | cat -n

Repository: nimblehq/ios-templates

Length of output: 1620


Avoid unsynchronized mutable capture between operation and onCancel closures.

request is mutated in the operation closure and read in onCancel; in Swift 6 strict concurrency this pattern triggers captured-mutable-state data-race diagnostics. Per Apple's documentation, the onCancel closure may execute concurrently with the operation. Wrap this shared state with a synchronization primitive (e.g., Mutex) or another concurrency-safe container.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@skills/swift-concurrency/references/structured-concurrency.md` around lines
185 - 197, The code mutates and reads the shared variable `request` from both
the operation passed to `withTaskCancellationHandler` and the `onCancel`
closure, which can run concurrently; to fix it make access to `request`
concurrency-safe by moving it into a synchronization boundary (for example an
actor or a simple lock/Mutex) and use that boundary from both
`withCheckedThrowingContinuation` (the operation that calls
`legacySession.dataTask`) and the `onCancel` closure so reads/writes to
`request` are serialized; update references to `request`,
`withTaskCancellationHandler`, `onCancel`, and `withCheckedThrowingContinuation`
accordingly so cancellation calls `cancel()` via the synchronized container.

@phongvhd93 phongvhd93 force-pushed the feature/#690-skill-for-swift6 branch from 67dec10 to f3a952d Compare April 16, 2026 07:46
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
skills/swift-concurrency/references/structured-concurrency.md (1)

185-197: ⚠️ Potential issue | 🟠 Major

Avoid mutable shared capture between operation and onCancel.

Line 185’s request is mutated in the operation closure and accessed in onCancel; in Swift 6 this can be flagged as unsafe concurrent capture. Store/cancel through a synchronization boundary (e.g., Mutex or actor wrapper).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@skills/swift-concurrency/references/structured-concurrency.md` around lines
185 - 197, The closure captures and mutates the local var `request` in the
operation passed to `withTaskCancellationHandler` while `onCancel` reads/calls
`request?.cancel()`, which creates an unsafe concurrent capture in Swift 6; fix
by introducing a synchronization boundary (e.g., wrap access to the task in a
small actor or Mutex) and use that wrapper from both the
`withCheckedThrowingContinuation` body (where you assign the result of
`legacySession.dataTask`) and the `onCancel` closure (where you call
`cancel()`), keeping `request` itself non-mutated outside the actor/Mutex and
referencing the actor/Mutex methods instead in
`withTaskCancellationHandler`/`onCancel`.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@skills/swift-concurrency/references/actors.md`:
- Around line 103-106: Update the rule for nonisolated to emphasize access
restrictions rather than type constraints: state that nonisolated members
(computed properties and methods) must not access any actor-isolated state and
may only use nonisolated/static data or values captured/created locally; mention
that reading a Sendable value does not by itself make access to isolated state
safe, and keep the existing notes that nonisolated is safe for let stored
properties but never allowed on var stored properties (compiler rejects it).

In `@skills/swift-concurrency/references/async-sequences.md`:
- Around line 164-165: Update the sentence that says "`@Sendable` is required"
to a softer, conditional recommendation: explain that the `onTermination`
closure is already declared `@Sendable` in the API type signature and that
explicitly adding `@Sendable` on the closure is recommended only when the
compiler cannot infer it (or emits a warning), rather than being strictly
required; reference the `onTermination` parameter and the `@Sendable` annotation
in this wording so readers can locate the API signature and understand when to
add the annotation.

In `@skills/swift-concurrency/references/structured-concurrency.md`:
- Around line 88-91: The async sequence iteration is using a synchronous for
loop which bypasses async backpressure; in the withDiscardingTaskGroup closure
replace the plain "for event in eventStream" with an asynchronous iteration "for
await event in eventStream" so each element is awaited before scheduling
group.addTask { await process(event) }, ensuring correct bounded concurrency for
eventStream and preserving the intended behavior of withDiscardingTaskGroup and
process(event).

---

Duplicate comments:
In `@skills/swift-concurrency/references/structured-concurrency.md`:
- Around line 185-197: The closure captures and mutates the local var `request`
in the operation passed to `withTaskCancellationHandler` while `onCancel`
reads/calls `request?.cancel()`, which creates an unsafe concurrent capture in
Swift 6; fix by introducing a synchronization boundary (e.g., wrap access to the
task in a small actor or Mutex) and use that wrapper from both the
`withCheckedThrowingContinuation` body (where you assign the result of
`legacySession.dataTask`) and the `onCancel` closure (where you call
`cancel()`), keeping `request` itself non-mutated outside the actor/Mutex and
referencing the actor/Mutex methods instead in
`withTaskCancellationHandler`/`onCancel`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 30cdd27f-b80d-4d2e-a45b-7d004f427e0a

📥 Commits

Reviewing files that changed from the base of the PR and between 67dec10 and f3a952d.

📒 Files selected for processing (5)
  • skills/swift-concurrency/SKILL.md
  • skills/swift-concurrency/references/actors.md
  • skills/swift-concurrency/references/async-sequences.md
  • skills/swift-concurrency/references/sendability.md
  • skills/swift-concurrency/references/structured-concurrency.md

Comment on lines +103 to +106
Rules for `nonisolated`:
- Safe on `let` stored properties (immutable — no data race possible).
- Safe on computed properties and methods that only read immutable or `Sendable` values.
- **Never** apply `nonisolated` to a `var` stored property — the compiler will reject it.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's verify the exact content at the specified lines
head -120 skills/swift-concurrency/references/actors.md | tail -25

Repository: nimblehq/ios-templates

Length of output: 943


🌐 Web query:

Swift nonisolated Sendable actor isolation semantics rules

💡 Result:

In Swift concurrency, actors provide isolation for their mutable state. By default, all instance members (methods, properties, subscripts) of an actor are isolated to that actor, meaning they execute on the actor's executor and can access isolated state synchronously. The nonisolated keyword opts out of this isolation for actor members, making them synchronously callable from any context without hopping to the actor's executor. Nonisolated members cannot synchronously access the actor's isolated state (e.g., other isolated properties or methods); attempts to do so result in compiler errors. Key semantics and rules for nonisolated in actors: - Access to state: Nonisolated members can only reference nonisolated state or global/nonisolated data. Accessing isolated self members requires await. Examples include nonisolated let properties (immutable, safe to share) or computed properties based solely on nonisolated data. - Sendable requirements: Since nonisolated members can be called concurrently from multiple isolation domains (e.g., different actors or nonisolated contexts), all types used within them (parameters, return types, captured values in closures) must conform to Sendable. The compiler enforces this to prevent data races. Non-Sendable types in nonisolated contexts trigger errors like "Sending 'nonSendable' risks causing data races". - nonisolated(unsafe): Allows synchronous access to isolated state from nonisolated contexts, bypassing safety checks. Use only when you manually ensure thread-safety (e.g., no data races). This opts out of compiler enforcement, similar to @unchecked Sendable. - Protocol conformance: Nonisolated members enable actors to conform to synchronous protocols (e.g., Hashable, CustomStringConvertible) by implementing requirements using only nonisolated state. - Stored properties: - nonisolated let of Sendable type: Safe, accessible synchronously from anywhere. - nonisolated var: Allowed if Sendable; for global-actor-isolated value types (e.g., @MainActor struct), nonisolated inference applies within-module for Sendable vars without (unsafe). - Non-Sendable stored properties cannot be nonisolated unless the enclosing type ensures exclusive access. - Functions/closures: Nonisolated functions in actors are synchronous by default unless async. @Sendable may be inferred or required for escaping closures. In Swift 6+, region-based isolation (SE-0414) refines checks for safe non-Sendable passing if no overlapping lifetimes. Examples: actor Counter { private var value = 0 // isolated nonisolated let id: Int // OK: immutable Sendable nonisolated func getId -> Int { id } // OK: uses nonisolated state nonisolated(unsafe) var unsafeValue: Int { value } // Unsafe: bypasses isolation } These rules (from SE-0306, SE-0313) ensure nonisolated access is safe when state is immutable/Sendable, while isolated access protects mutable state. Prioritize official Swift Evolution proposals and Apple docs for latest details, as concurrency evolves (e.g., Swift 6.2 introduces nonisolated(nonsending) defaults).

Citations:


Clarify nonisolated rule for computed properties and methods to avoid Sendable-only misinterpretation.

The current wording "only read immutable or Sendable values" focuses on type constraints but obscures the core safety rule. The fundamental requirement is that nonisolated members must not access actor-isolated state at all—they can only use nonisolated or static data. Clarifying this prevents readers from incorrectly thinking that merely reading Sendable values is sufficient for nonisolated safety.

Suggested wording
-- Safe on computed properties and methods that only read immutable or `Sendable` values.
+- Safe on computed properties and methods that do not access actor-isolated stored state (they may use only nonisolated/static data).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Rules for `nonisolated`:
- Safe on `let` stored properties (immutable — no data race possible).
- Safe on computed properties and methods that only read immutable or `Sendable` values.
- **Never** apply `nonisolated` to a `var` stored property — the compiler will reject it.
Rules for `nonisolated`:
- Safe on `let` stored properties (immutable — no data race possible).
- Safe on computed properties and methods that do not access actor-isolated stored state (they may use only nonisolated/static data).
- **Never** apply `nonisolated` to a `var` stored property — the compiler will reject it.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@skills/swift-concurrency/references/actors.md` around lines 103 - 106, Update
the rule for nonisolated to emphasize access restrictions rather than type
constraints: state that nonisolated members (computed properties and methods)
must not access any actor-isolated state and may only use nonisolated/static
data or values captured/created locally; mention that reading a Sendable value
does not by itself make access to isolated state safe, and keep the existing
notes that nonisolated is safe for let stored properties but never allowed on
var stored properties (compiler rejects it).

Comment on lines +164 to +165
The `@Sendable` annotation on the closure is required because `onTermination` can be called from any concurrency domain.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, locate and read the file at the specified lines
fd -e md -path "*async-sequences.md"

Repository: nimblehq/ios-templates

Length of output: 234


🏁 Script executed:

# Read lines 160-170 to get context around line 164-165
sed -n '160,170p' "skills/swift-concurrency/references/async-sequences.md"

Repository: nimblehq/ios-templates

Length of output: 378


🌐 Web query:

Swift AsyncStream.Continuation.onTermination closure Sendable annotation API

💡 Result:

The onTermination property of Swift.AsyncStream.Continuation is declared as: var onTermination: (@Sendable (AsyncStream.Continuation.Termination) -> Void)? { get nonmutating set } This API requires the closure assigned to onTermination to conform to the @Sendable protocol, meaning it must be safe to call from any concurrency domain (actor-isolated or non-isolated context). The @Sendable annotation ensures thread-safety for the callback, which is invoked when the stream iteration is cancelled or finished. The same applies to AsyncThrowingStream.Continuation.onTermination: var onTermination: (@Sendable (AsyncThrowingStream<Element, Failure>.Continuation.Termination) -> Void)? { get nonmutating set } When assigning a closure, if the compiler warns about converting a non-concurrent closure to @Sendable, use @Sendable in the closure signature, e.g.: continuation.onTermination = { @Sendable termination in // Handle .finished or .cancelled } This is the official API design for cleanup during stream termination, as documented in Apple's Swift documentation (current as of 2026).

Citations:


Soften "required" to "recommended for clarity" in the @Sendable explanation.

Line 164 incorrectly states the @Sendable annotation is required, but the onTermination closure parameter is already declared as @Sendable in the API type signature. Explicit annotation is only necessary if the compiler warns about type inference, not unconditionally. Suggest updating to reflect this conditional nature:

Suggested wording
-The `@Sendable` annotation on the closure is required because `onTermination` can be called from any concurrency domain.
+`onTermination` closures are `@Sendable` (explicit annotation is recommended for clarity) because they can be called from any concurrency domain.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
The `@Sendable` annotation on the closure is required because `onTermination` can be called from any concurrency domain.
`onTermination` closures are `@Sendable` (explicit annotation is recommended for clarity) because they can be called from any concurrency domain.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@skills/swift-concurrency/references/async-sequences.md` around lines 164 -
165, Update the sentence that says "`@Sendable` is required" to a softer,
conditional recommendation: explain that the `onTermination` closure is already
declared `@Sendable` in the API type signature and that explicitly adding
`@Sendable` on the closure is recommended only when the compiler cannot infer it
(or emits a warning), rather than being strictly required; reference the
`onTermination` parameter and the `@Sendable` annotation in this wording so
readers can locate the API signature and understand when to add the annotation.

Comment on lines +88 to +91
await withDiscardingTaskGroup { group in
for event in eventStream {
group.addTask { await process(event) }
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n skills/swift-concurrency/references/structured-concurrency.md | sed -n '80,100p'

Repository: nimblehq/ios-templates

Length of output: 892


🏁 Script executed:

cat -n skills/swift-concurrency/references/structured-concurrency.md | head -90 | tail -40

Repository: nimblehq/ios-templates

Length of output: 1415


🏁 Script executed:

grep -n "eventStream" skills/swift-concurrency/references/structured-concurrency.md

Repository: nimblehq/ios-templates

Length of output: 99


🏁 Script executed:

cat -n skills/swift-concurrency/references/structured-concurrency.md | sed -n '83,110p'

Repository: nimblehq/ios-templates

Length of output: 1030


🏁 Script executed:

cat -n skills/swift-concurrency/references/structured-concurrency.md | head -1
wc -l skills/swift-concurrency/references/structured-concurrency.md

Repository: nimblehq/ios-templates

Length of output: 163


🏁 Script executed:

cat skills/swift-concurrency/references/structured-concurrency.md

Repository: nimblehq/ios-templates

Length of output: 7364


🌐 Web query:

Swift AsyncSequence for await syntax requirements

💡 Result:

To use the for await-in syntax with Swift's AsyncSequence, a type must conform to the AsyncSequence protocol. This requires: 1. Defining an associated type Element for the type of values produced. 2. Defining an associated type AsyncIterator that conforms to AsyncIteratorProtocol where AsyncIterator.Element == Element. 3. Implementing the required method: func makeAsyncIterator -> AsyncIterator The AsyncIteratorProtocol requires: mutating func next async throws -> Element? This next method asynchronously returns the next element or nil to end the sequence. The for await-in loop (e.g., for await element in asyncSequence { ... }) is only valid in an async context and handles throws appropriately with try if needed. In Swift 6+, AsyncSequence<Element, Failure> and AsyncIteratorProtocol<Element, Failure> support typed throws with Failure: Error, and an optional next(isolation:) overload for actor isolation, but existing conformances remain compatible via defaults. Example conformance: struct Counter: AsyncSequence { typealias Element = Int let howHigh: Int struct AsyncIterator: AsyncIteratorProtocol { let howHigh: Int var current = 1 mutating func next async -> Int? { guard current <= howHigh else { return nil } let result = current current += 1 return result } } func makeAsyncIterator -> AsyncIterator { AsyncIterator(howHigh: howHigh) } } // Usage in async context: for await number in Counter(howHigh: 10) { print(number) }

Citations:


Fix async sequence iteration to use for await.

The example (line 89) uses synchronous for iteration, but with eventStream as an async sequence, this pattern would not provide the intended backpressure (bounded concurrency). It should be for await event in eventStream to properly iterate through the async stream.

Suggested fix
 await withDiscardingTaskGroup { group in
-    for event in eventStream {
+    for await event in eventStream {
         group.addTask { await process(event) }
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@skills/swift-concurrency/references/structured-concurrency.md` around lines
88 - 91, The async sequence iteration is using a synchronous for loop which
bypasses async backpressure; in the withDiscardingTaskGroup closure replace the
plain "for event in eventStream" with an asynchronous iteration "for await event
in eventStream" so each element is awaited before scheduling group.addTask {
await process(event) }, ensuring correct bounded concurrency for eventStream and
preserving the intended behavior of withDiscardingTaskGroup and process(event).

Comment on lines +130 to +137
AsyncThrowingStream { continuation in
Task {
defer { continuation.finish() }

guard let data = try? await fetch() else { return }
continuation.yield(data)
}
}
Copy link
Copy Markdown

@Thieurom Thieurom Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an AsyncThrowingStream but it doesn't propagate the throwing error. It just always finish?

Comment on lines +12 to +14
- Callers outside the actor must `await` to access isolated members.
- No two tasks can be inside the actor concurrently.

Copy link
Copy Markdown

@Thieurom Thieurom Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No two tasks can be inside the actor concurrently. and the below section Actor reentrancy contradict to each other. Another task can enter while the actor is suspended as stated on Actor reentrancy is correct I believe 🙏

Comment on lines +166 to +168
let task = Task {
try await networkSession.data(from: url).0
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This child task is not structured under the parent. Can you check again if it's really that later parent cancellation is automatically propagated to it?

Comment on lines +1 to +3
---
name: swift6-concurrency
description: >
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the skill name swift6-concurrency or swift-concurrency? Either way, I think the name of the skill and that of its directory should match.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add swift concurrency skill

4 participants