<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Joshua Brackin</title>
    <description>The latest articles on DEV Community by Joshua Brackin (@jbrackin).</description>
    <link>https://dev.to/jbrackin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3997280%2F60f524c7-0dc3-4ecf-8f47-054f023c16d0.png</url>
      <title>DEV Community: Joshua Brackin</title>
      <link>https://dev.to/jbrackin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jbrackin"/>
    <language>en</language>
    <item>
      <title>The test that catches the race your agent silenced</title>
      <dc:creator>Joshua Brackin</dc:creator>
      <pubDate>Mon, 06 Jul 2026 17:15:42 +0000</pubDate>
      <link>https://dev.to/jbrackin/the-test-that-catches-the-race-your-agent-silenced-29gc</link>
      <guid>https://dev.to/jbrackin/the-test-that-catches-the-race-your-agent-silenced-29gc</guid>
      <description>&lt;p&gt;Last week I showed an agent making a Swift 6 data race disappear with one word. It marked a struct &lt;code&gt;@unchecked Sendable&lt;/code&gt;, the build went green, every test still passed, and the race was exactly as present as before. The post ended on the question I didn't have a clean answer to: when the build and the tests both go green on a silenced race, what in the loop is supposed to catch it?&lt;/p&gt;

&lt;p&gt;A red build is a signal you can trust. A silenced race hands you a green build you can't, and the only thing between you and shipping it is you, reading the diff. I spent this week building tests that put the red back on the board. Here is the one that worked, and the honest edges of it.&lt;/p&gt;

&lt;p&gt;The setup from last time: a value type that crosses into concurrent code, carrying a mutable class the agent no longer wanted the compiler to complain about.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;AuditPen&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;ink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ink&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@unchecked&lt;/span&gt; &lt;span class="kt"&gt;Sendable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;pen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuditPen&lt;/span&gt;   &lt;span class="c1"&gt;// mutable, shared, and now unchecked&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the test a person writes for this without thinking about concurrency at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@Test&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;happyPath&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"rent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuditPen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="cp"&gt;#expect(t.amount == 100)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It builds. It passes. It will pass on the silenced version forever, because it never once runs two things at the same time. The assertion is about the amount. The bug is about &lt;code&gt;pen&lt;/code&gt;. They never meet. That is the shape of almost every test sitting next to a silenced race. It checks a value on the happy path, and the race needs a second writer on the board before anything goes wrong.&lt;/p&gt;

&lt;p&gt;So put the second writer into the test setup itself, and assert the thing the race breaks. The test's job is to create the exact situation the &lt;code&gt;Sendable&lt;/code&gt; promise was about: more than one task writing &lt;code&gt;pen&lt;/code&gt; at the same time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@Test&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;concurrentBumpsMustNotLoseUpdates&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"rent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuditPen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concurrentPerform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pen&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ink&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="cp"&gt;#expect(t.pen.ink == n)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;concurrentPerform&lt;/code&gt; runs the block across several threads. Each one reads &lt;code&gt;ink&lt;/code&gt;, adds one, and writes it back, with nothing coordinating them. When two threads land on the same starting value, one of the updates is lost. On the silenced &lt;code&gt;@unchecked Sendable&lt;/code&gt; version this fails every single time I run it. The count comes back short, sometimes by a handful, sometimes by hundreds, never exactly the target. The amount you lose wobbles from run to run. The failure does not.&lt;/p&gt;

&lt;p&gt;Here is what makes that a real check and not just a way to make concurrent code look bad. Fix the type properly and the same test goes green and stays green.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;AuditPen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@unchecked&lt;/span&gt; &lt;span class="kt"&gt;Sendable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;_ink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ink&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;bump&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;_ink&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;ink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_ink&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;@unchecked Sendable&lt;/code&gt; has moved down to where the synchronization actually lives, and it finally means something. There is a lock behind the promise. &lt;code&gt;Transfer&lt;/code&gt; can drop back to a plain &lt;code&gt;Sendable&lt;/code&gt;. The concurrent test passes a hundred runs out of a hundred, because the updates can no longer step on each other. That is the property I want from the test: red when the type only claims to be safe, green when it is. The agent writes good Swift. What it can't do is tell a real &lt;code&gt;Sendable&lt;/code&gt; conformance from a hollow one, and that gap is exactly what this test pins down.&lt;/p&gt;

&lt;p&gt;If you know this space you have been waiting for me to say ThreadSanitizer. It is the textbook answer, and in theory it is the better one. TSan watches memory at runtime and reports the race directly, even on the odd run where the lost updates cancel out and the count happens to come back right. The behavioral test can miss that run. TSan will not.&lt;/p&gt;

&lt;p&gt;I tried to make TSan the center of this post. On my current toolchain I could not get it to run. &lt;code&gt;swift test --sanitize=thread&lt;/code&gt; builds and then dies before a single test executes, because the sanitizer's own runtime library refuses to load: a code-signing and platform-policy rejection on the toolchain's copy of the dylib. A standalone executable built with &lt;code&gt;-sanitize=thread&lt;/code&gt; fails the same way. That is a setup problem specific to this Xcode, not a verdict on TSan, but it is the reason I lead with the plain test. The behavioral version needs nothing but the test runner you already have. When TSan is wired up it is a real upgrade and it belongs in CI on anything concurrency-heavy. Wiring it up is its own afternoon, and it depends on your Xcode version cooperating.&lt;/p&gt;

&lt;p&gt;None of this is a fix for the general problem, and I want to be exact about why. The test only catches the adversary you thought to write. If nobody writes the concurrent-writer case, there is no red to chase and you are back to reading diffs by hand. The assertion is only as sharp as the contention it creates. On a debug build the losses can be small, and if you do not push the concurrency hard enough a race can still slip through on a quiet run. And it does nothing for the other failure modes. An agent that builds the wrong feature cleanly will sail through every adversarial concurrency test you own.&lt;/p&gt;

&lt;p&gt;What it buys is narrow and real. For the one failure mode where the build and the existing tests both lie to you, it gives the loop something to fail on that a confident paragraph can't argue its way past. The agent can write &lt;code&gt;@unchecked Sendable&lt;/code&gt; and explain why it was the reasonable call. It cannot make a thousand concurrent increments add up to a thousand without actually doing the synchronization.&lt;/p&gt;

&lt;p&gt;So here is where I have landed and what I am still missing. For concurrency-sensitive types, do you hand-write these adversary-carrying tests, or have you found a way to generate the "exercise it under contention" case for every &lt;code&gt;Sendable&lt;/code&gt; you declare? And if you run TSan in CI, did your toolchain just work, or did you have to fight it to get green?&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ios</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Watch a coding agent silence a Swift 6 data race instead of fixing it</title>
      <dc:creator>Joshua Brackin</dc:creator>
      <pubDate>Mon, 29 Jun 2026 16:27:40 +0000</pubDate>
      <link>https://dev.to/jbrackin/watch-a-coding-agent-silence-a-swift-6-data-race-instead-of-fixing-it-4jpp</link>
      <guid>https://dev.to/jbrackin/watch-a-coding-agent-silence-a-swift-6-data-race-instead-of-fixing-it-4jpp</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;-strict-concurrency=complete&lt;/code&gt; is standing right there to tell them apart.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Here's a concrete one. A value type that crosses into concurrent code, declared &lt;code&gt;Sendable&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Sendable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now someone adds a stored property whose type is a mutable class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;AuditPen&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;ink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ink&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Sendable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;pen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuditPen&lt;/span&gt;   &lt;span class="c1"&gt;// mutable reference type&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build breaks, correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stored property 'pen' of 'Sendable'-conforming struct 'Transfer'
has non-sendable type 'AuditPen'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That error is doing its job. &lt;code&gt;Transfer&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;The fix the agent reaches for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@unchecked&lt;/span&gt; &lt;span class="kt"&gt;Sendable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One word, &lt;code&gt;@unchecked&lt;/code&gt;. Green build. Every test still passes, because the tests never exercised concurrent mutation of &lt;code&gt;pen&lt;/code&gt;. And the race is exactly as present as it was a minute ago, now with the compiler told to stop mentioning it. &lt;code&gt;@unchecked Sendable&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;I want to be fair to the keyword, because the honest version of this is more interesting than "agent dumb." &lt;code&gt;@unchecked Sendable&lt;/code&gt; is a real, correct tool. If &lt;code&gt;AuditPen&lt;/code&gt; guarded every access to &lt;code&gt;ink&lt;/code&gt; behind a lock, marking the wrapper &lt;code&gt;@unchecked Sendable&lt;/code&gt; 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 &lt;code&gt;@unchecked Sendable&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;Sendable&lt;/code&gt; conformance stays honest.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;if #available&lt;/code&gt;, the agent deletes the &lt;code&gt;@available&lt;/code&gt; line. A function is typed &lt;code&gt;throws(NetworkError)&lt;/code&gt; and the agent throws the wrong error, so rather than fix what it throws it widens the signature to a plain &lt;code&gt;throws&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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. &lt;code&gt;@unchecked Sendable&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;If you run agents against Swift 6 work, where have you landed on this? Do you scan the diffs for &lt;code&gt;@unchecked Sendable&lt;/code&gt; and &lt;code&gt;nonisolated(unsafe)&lt;/code&gt; by hand, or have you found a way to make the loop itself refuse a fix that only silences the checker?&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ios</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Coding agents are good at writing Swift. They're bad at finishing it.</title>
      <dc:creator>Joshua Brackin</dc:creator>
      <pubDate>Mon, 22 Jun 2026 16:52:48 +0000</pubDate>
      <link>https://dev.to/jbrackin/coding-agents-are-good-at-writing-swift-theyre-bad-at-finishing-it-md3</link>
      <guid>https://dev.to/jbrackin/coding-agents-are-good-at-writing-swift-theyre-bad-at-finishing-it-md3</guid>
      <description>&lt;p&gt;I've spent the last few months pointing AI coding agents at real Swift and Xcode work and watching where they come apart. Not "write me a login screen" demos. Tasks with a build, a test target, and a finish line the agent has to reach on its own.&lt;/p&gt;

&lt;p&gt;Start with the part that surprised me: the first draft is usually fine.&lt;/p&gt;

&lt;p&gt;Give a capable model a reasonable Swift task and the code it writes on the first pass is often correct, or close. The view is sensible. The types line up. If writing Swift were the bottleneck, these tools would already be done.&lt;/p&gt;

&lt;p&gt;Worth saying plainly, because a certain kind of post likes to claim the models can't write Swift. They can. They're good at it and getting better. So the interesting question is what happens after that first draft, in the gap between "looks finished" and "is actually right."&lt;/p&gt;

&lt;p&gt;The loud version of the build-loop complaint also gets something wrong: on a modern harness, the pure "won't compile" loop is mostly handled. Claude Code and Codex won't accept their own work while the build is red. They churn on a compile error quietly and hand you something that builds. If your agent still ships you red builds, that's a harness problem with a known fix.&lt;/p&gt;

&lt;p&gt;The failures that survive are the ones the compiler can't see. Those are the ones that cost me time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It builds fine and isn't what I asked for.&lt;/strong&gt; The most common one now. The code compiles, the tests it wrote pass, and the behavior is subtly or completely wrong against what I actually wanted. The agent has no way to check intent. Green is not the same as correct, and green is the only thing the agent knows how to chase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It compiles and races.&lt;/strong&gt; Concurrency is the sharp version of this. Swift's compiler catches a lot, but you can still get code that builds clean and has a data race that only shows up under certain timing. The agent reads the green build as success and moves on. When the failure does surface, it usually wants a small redesign rather than a one-line fix, and the redesign is exactly the move the agent won't reach for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It fixes one thing and quietly breaks another, then loops.&lt;/strong&gt; This is the one that eats the most of my afternoon. The agent lands a real fix, hits a different problem, and while chasing the second problem it undoes the first. A few turns later it's back to something it already solved. Left running long enough I've watched it oscillate between two broken states: A breaks B, the fix for B brings back A, around and around. It has no durable sense of "I tried that and it didn't work."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It writes code I wouldn't ship.&lt;/strong&gt; Compiles, runs, still the wrong shape. A pattern that fights the framework. A structure that ignores how the rest of the app is built. Fine for a throwaway. Not fine in code I have to live with.&lt;/p&gt;

&lt;p&gt;None of these are language problems. The model knows Swift. What it's missing is everything the compiler can't tell it: whether the result matches what I meant, whether it holds up at runtime, and whether it's the kind of code an experienced developer would keep.&lt;/p&gt;

&lt;p&gt;That last gap is where the real expense lives, and it's not the one people reach for first. The talk is usually about token cost. What I actually feel is attention. An agent that writes good Swift and then needs me watching it, stepping in every few turns to keep it from circling, hasn't saved me the work. It's converted writing into supervising. Some days that's a fine trade. A lot of days it means the thing only really runs when I'm sitting next to it.&lt;/p&gt;

&lt;p&gt;I don't have this solved. What I can say is that the failure modes are consistent enough to name, which is more than I expected when I started measuring them. The progress I've made has come from changing the loop around the model, what it checks and what it remembers between turns, more than from swapping in a smarter model.&lt;/p&gt;

&lt;p&gt;So I want to know whether this matches your experience. For those of you running agents against real Apple work: where does it actually break for you now that the build mostly takes care of itself? The intent mismatches, the races that compile, or something I'm not watching for yet?&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ios</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
