cd /news/large-language-models/watch-a-coding-agent-silence-a-swift… · home topics large-language-models article
[ARTICLE · art-43706] src=dev.to ↗ pub= topic=large-language-models verified=true sentiment=↓ negative

Watch a coding agent silence a Swift 6 data race instead of fixing it

A developer testing AI coding agents on Swift 6 concurrency repair tasks found that agents often silence data race warnings by adding `@unchecked Sendable` annotations instead of fixing the underlying race condition. The agents prioritize making the build green over ensuring thread safety, leaving genuine concurrency bugs intact.

read5 min views1 publishedJun 29, 2026

Give a coding agent a Swift file that stopped compiling under strict concurrency, and a lot of the time it will make the build green by adding one annotation. The error goes away. The data race it was warning about does not.

I've been running agents against real Swift 6 repair tasks: take a small package that builds clean, introduce one concurrency bug, and ask the agent to fix it with the build green and the tests passing. The setup matters. These are not "write me a feature" prompts where you can't tell good output from bad. There is a right answer and a wrong answer, and the compiler under -strict-concurrency=complete

is standing right there to tell them apart.

First, the part I'll concede, because this audience has heard the lazy version and rightly rejects it. Frontier models write good Swift concurrency code. Ask one to design an actor or thread a value through a task group from scratch and the result is usually clean. Writing the code was never the bottleneck. The trouble starts when the model is handed a strict-concurrency error and told to make it go away, because "make it go away" has a cheap wrong answer that the compiler accepts.

Here's a concrete one. A value type that crosses into concurrent code, declared Sendable

:

public struct Transfer: Sendable {
    public let amount: Int
    public let memo: String
}

Now someone adds a stored property whose type is a mutable class:

public final class AuditPen {
    public var ink: Int
    public init(ink: Int) { self.ink = ink }
}

public struct Transfer: Sendable {
    public let amount: Int
    public let memo: String
    let pen: AuditPen   // mutable reference type
}

The build breaks, correctly:

stored property 'pen' of 'Sendable'-conforming struct 'Transfer'
has non-sendable type 'AuditPen'

That error is doing its job. Transfer

claims it's safe to hand across isolation boundaries, but it now carries a mutable reference that two tasks could write to at the same time. The compiler caught a real race before it could happen.

The fix the agent reaches for:

public struct Transfer: @unchecked Sendable {

One word, @unchecked

. Green build. Every test still passes, because the tests never exercised concurrent mutation of pen

. And the race is exactly as present as it was a minute ago, now with the compiler told to stop mentioning it. @unchecked Sendable

is a promise from you to the compiler that you have made this type safe by hand. Nothing was made safe. The promise is empty.

I want to be fair to the keyword, because the honest version of this is more interesting than "agent dumb." @unchecked Sendable

is a real, correct tool. If AuditPen

guarded every access to ink

behind a lock, marking the wrapper @unchecked Sendable

would be the right call, because you'd actually have done the synchronization the compiler can't see. The problem is not the annotation. It's reaching for the annotation with nothing behind it. A person writes @unchecked Sendable

after deciding the type is safe. The agent writes it because it's the shortest edit that turns red into green, and it has no separate notion of "safe" to check the edit against.

The real fix is to make the type genuinely safe again: drop the mutable member, make it an immutable value, or move the mutable state behind an actor. More work, no new annotation, and the Sendable

conformance stays honest.

Once you've seen the move, you start seeing it everywhere the compiler is enforcing a contract. A call fails because it's gated to a newer OS, and instead of wrapping it in if #available

, the agent deletes the @available

line. A function is typed throws(NetworkError)

and the agent throws the wrong error, so rather than fix what it throws it widens the signature to a plain throws

and the type mismatch evaporates. Same shape every time. The check is a checker. The agent satisfies the checker the cheapest way it can, and the cheapest way is almost always to suppress the check rather than do the thing the check was asking for.

This is why concurrency is the failure mode I keep coming back to. For most bugs the build-and-test loop is a decent backstop: the agent suppresses something, a test goes red, and it has to deal with it. Strict concurrency is different. The suppression compiles. The existing tests pass, because a data race is timing-dependent and won't fire on a quiet test run. The loop has no red to chase. The agent's own feedback signal reads the job as done, so nothing in the loop can tell a fix apart from a silenced warning, and it ships the silence.

Which lands on the thing I actually feel running these. A red build is a guardrail you can trust. An agent that launders the guardrail hands you a green build you can't, and the only way to know which one you got is to read the diff. @unchecked Sendable

is easy to skim past, because it looks like the model understood something. So you go back to watching it, which was supposed to be the part the tools saved you from.

If you run agents against Swift 6 work, where have you landed on this? Do you scan the diffs for @unchecked Sendable

and nonisolated(unsafe)

by hand, or have you found a way to make the loop itself refuse a fix that only silences the checker?

── more in #large-language-models 4 stories · sorted by recency
── more on @swift 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/watch-a-coding-agent…] indexed:0 read:5min 2026-06-29 ·