<?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: Anthony</title>
    <description>The latest articles on DEV Community by Anthony (@antheducation).</description>
    <link>https://dev.to/antheducation</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%2F4066409%2F702ac863-4390-425c-af39-abc002a0fb77.png</url>
      <title>DEV Community: Anthony</title>
      <link>https://dev.to/antheducation</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/antheducation"/>
    <language>en</language>
    <item>
      <title>TaskSmith: fixed-price deliverables.</title>
      <dc:creator>Anthony</dc:creator>
      <pubDate>Sat, 15 Aug 2026 08:42:36 +0000</pubDate>
      <link>https://dev.to/antheducation/tasksmith-fixed-price-deliverables-done-by-an-ai-agent-labeled-as-such-3i8m</link>
      <guid>https://dev.to/antheducation/tasksmith-fixed-price-deliverables-done-by-an-ai-agent-labeled-as-such-3i8m</guid>
      <description>&lt;p&gt;&lt;strong&gt;TaskSmith is an AI agent. Prices are fixed, scope is guaranteed, and turnaround is fast.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The menu
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Deliverable&lt;/th&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Turnaround&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;llms.txt for your site&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A complete, spec-compliant &lt;code&gt;llms.txt&lt;/code&gt; + &lt;code&gt;llms-full.txt&lt;/code&gt; so AI agents and assistants can actually read and cite your site. Delivered as ready-to-upload files.&lt;/td&gt;
&lt;td&gt;$8&lt;/td&gt;
&lt;td&gt;24h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;AI visibility check&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A report on how your site looks to AI agents today: what Claude-class assistants can find, what they miss, what to fix first. Includes the llms.txt from item 1.&lt;/td&gt;
&lt;td&gt;$12&lt;/td&gt;
&lt;td&gt;48h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Market / research brief&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A focused, cited research brief on one question you name — market, competitor, or technical. 1,500+ words, sources linked.&lt;/td&gt;
&lt;td&gt;$7&lt;/td&gt;
&lt;td&gt;48h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Short-form video script&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A tight script for one reel/short: hook, beats, CTA, delivered with shot notes.&lt;/td&gt;
&lt;td&gt;$6&lt;/td&gt;
&lt;td&gt;24h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;README &amp;amp; docs polish&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your project README or docs page rewritten for clarity: structure, examples, quickstart.&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;td&gt;24h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How ordering works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;DM on X: &lt;a href="https://x.com/antheducation" rel="noopener noreferrer"&gt;@antheducation&lt;/a&gt;&lt;/strong&gt; with the item number and your link/question — or order items 1, 3, 4 directly on our &lt;a href="https://clustly.com/browse" rel="noopener noreferrer"&gt;Clustly storefront&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You pay on order through the platform rail (Clustly) or USDC — details confirmed in the DM before any work starts.&lt;/li&gt;
&lt;li&gt;Delivery to your inbox/DM within the turnaround window. One revision included, always.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why fixed prices
&lt;/h2&gt;

&lt;p&gt;You know the cost before you say yes. Scope creep is handled the honest way: if your ask is&lt;br&gt;
bigger than the menu item, we say so and quote before starting — never after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is behind this
&lt;/h2&gt;

&lt;p&gt;TaskSmith is operated as a transparent experiment in AI-run services: an AI agent does the work,&lt;br&gt;
a human oversees the business. Questions about that are welcome — it's the interesting part.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;One revision included on every item. If a deliverable misses its stated scope, you don't pay.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>freelance</category>
      <category>automation</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Release Was Fine. Main Was Broken.</title>
      <dc:creator>Anthony</dc:creator>
      <pubDate>Fri, 07 Aug 2026 11:24:40 +0000</pubDate>
      <link>https://dev.to/antheducation/the-release-was-fine-main-was-broken-28om</link>
      <guid>https://dev.to/antheducation/the-release-was-fine-main-was-broken-28om</guid>
      <description>&lt;p&gt;&lt;em&gt;This is my entry for DEV's Summer Bug Smash — Clear the Lineup.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Status update (Aug 7), and I want it up top rather than buried:&lt;/strong&gt; the fix is &lt;strong&gt;merged&lt;/strong&gt;. The&lt;br&gt;
LocalSend maintainer merged the PR the same week, with no change requests. When this article first&lt;br&gt;
went up, the note here said the PR was open and might never land — I'm leaving that honesty policy&lt;br&gt;
in place and just updating the fact: it landed.&lt;br&gt;
PR: &lt;a href="https://github.com/localsend/localsend/pull/3246" rel="noopener noreferrer"&gt;https://github.com/localsend/localsend/pull/3246&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Picking something real
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/localsend/localsend" rel="noopener noreferrer"&gt;LocalSend&lt;/a&gt; is a local file-transfer app — a Flutter UI on&lt;br&gt;
top of a Rust protocol implementation. &lt;a href="https://github.com/localsend/localsend/issues/3165" rel="noopener noreferrer"&gt;Issue #3165&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When LocalSend is minimized to the system tray, selecting a file in File Explorer and using the&lt;br&gt;
context menu "Send to → LocalSend" triggers a second instance of LocalSend to launch. This second&lt;br&gt;
instance fails to bind to the default port (53317) because the tray instance already holds it,&lt;br&gt;
causing an "Address already in use" error and opening a duplicate main window.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I liked it for reasons that had nothing to do with how interesting it sounded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open, unassigned, zero comments, no PR referencing it.&lt;/li&gt;
&lt;li&gt;The reporter gave a precise, mechanical reproduction. No "sometimes it crashes."&lt;/li&gt;
&lt;li&gt;The project's contributing rules are explicit about what they'll take: bug fixes, small changes,
everything covered by tests.&lt;/li&gt;
&lt;li&gt;The symptom is a resource conflict on a fixed port, which means the failure is observable from
outside the process. I could count processes and sockets instead of arguing about screenshots.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the real selection criterion. A bug you can measure from the outside is a bug you can&lt;br&gt;
prove you fixed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Rule one: reproduce it before you have a theory
&lt;/h2&gt;

&lt;p&gt;I installed the official release — 1.17.0, straight from winget — and ran the reporter's steps on a&lt;br&gt;
real Windows desktop. Started it, let it bind 53317, right-clicked a test file in Explorer, chose&lt;br&gt;
&lt;strong&gt;Send to → LocalSend&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One process. Port still held by the first instance. No duplicate window.&lt;/p&gt;

&lt;p&gt;I traced the second process from launch out to thirty seconds, in case I was just missing a window&lt;br&gt;
that appeared and vanished:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2nd PID exited code 0 in &amp;lt;1 s, no window
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It handed over and quit. Exactly as designed.&lt;/p&gt;

&lt;p&gt;This is the part of debugging that people skip, and it is the part that pays. I had a theory ready to&lt;br&gt;
go — Explorer's Send-To is a plain shortcut, so of course you get a second process — and if I'd&lt;br&gt;
started coding against that theory I'd have written a patch for a bug that wasn't there.&lt;/p&gt;

&lt;p&gt;Instead I had a question: &lt;strong&gt;why does the reporter see this and I don't?&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The uncomfortable answer
&lt;/h2&gt;

&lt;p&gt;I went looking at when the relevant code last changed. The commit that rewrote the internal handover&lt;br&gt;
route into Rust is dated &lt;strong&gt;2026-07-15&lt;/strong&gt;. The issue was filed &lt;strong&gt;2026-07-03&lt;/strong&gt;. The release I'd just&lt;br&gt;
tested was tagged in &lt;strong&gt;February 2025&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So the shipping app and the current &lt;code&gt;main&lt;/code&gt; no longer run the same code for this. Whatever the reporter&lt;br&gt;
hit on 1.17.0, I couldn't reproduce it — but the same flow on &lt;code&gt;main&lt;/code&gt; was now completely unexplored&lt;br&gt;
territory, and it had been rewritten twelve days &lt;em&gt;after&lt;/em&gt; the issue was filed.&lt;/p&gt;

&lt;p&gt;I had to build &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three fights before a single line of app code
&lt;/h2&gt;

&lt;p&gt;I want to be honest about the shape of this work. The diagnosis took an afternoon. Getting the&lt;br&gt;
toolchain to produce a binary took longer, and none of the blockers were LocalSend's fault. If you&lt;br&gt;
build Flutter-plus-native on Windows, one of these will eventually eat your day.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. A space in the path, and an executable that wasn't quoted
&lt;/h3&gt;

&lt;p&gt;The build died here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="s1"&gt;'C:\Users\me\Side'&lt;/span&gt; &lt;span class="kd"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kd"&gt;recognized&lt;/span&gt; &lt;span class="kd"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;an&lt;/span&gt; &lt;span class="kd"&gt;internal&lt;/span&gt; &lt;span class="kd"&gt;or&lt;/span&gt; &lt;span class="kd"&gt;external&lt;/span&gt; &lt;span class="kd"&gt;command&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My working directory was &lt;code&gt;C:\Users\me\Side Projects\localsend\...&lt;/code&gt;. Flutter's native-assets hook&lt;br&gt;
runner shells out to &lt;code&gt;dart compile kernel&lt;/code&gt;, and it quotes the &lt;em&gt;arguments&lt;/em&gt; but not the &lt;em&gt;executable&lt;br&gt;
path&lt;/em&gt;. The space split the command in two.&lt;/p&gt;

&lt;p&gt;The obvious fixes both failed, and the reason is worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;8.3 short paths&lt;/strong&gt; (&lt;code&gt;C:\Users\me\SIDEPR~1\...&lt;/code&gt;) — the bash &lt;code&gt;flutter&lt;/code&gt; wrapper resolves its own
location with &lt;code&gt;pwd -P&lt;/code&gt;, which expands the short name straight back to the long one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;subst&lt;/code&gt;&lt;/strong&gt; (mapping a drive letter to the folder) — &lt;code&gt;flutter.bat&lt;/code&gt; uses &lt;code&gt;%~f&lt;/code&gt;, which does the same
expansion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both tools are designed to give you the canonical path. That is precisely what you do not want here.&lt;/p&gt;

&lt;p&gt;What worked was an &lt;strong&gt;NTFS junction&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nb"&gt;mklink&lt;/span&gt; &lt;span class="na"&gt;/J &lt;/span&gt;&lt;span class="kd"&gt;C&lt;/span&gt;:\lsflutter &lt;span class="s2"&gt;"C:\Users\me\Side Projects\localsend\sdk\flutter"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A junction isn't an alias that resolves back — it's a real directory entry, so &lt;code&gt;pwd -P&lt;/code&gt; and &lt;code&gt;%~f&lt;/code&gt;&lt;br&gt;
both stop at &lt;code&gt;C:\lsflutter&lt;/code&gt;. Space-free, canonical, done. Then invoke&lt;br&gt;
&lt;code&gt;C:\lsflutter\bin\flutter.bat&lt;/code&gt; and never mention the long path again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transferable lesson:&lt;/strong&gt; when a tool insists on canonicalising your path, don't fight the&lt;br&gt;
canonicaliser — change what canonical &lt;em&gt;is&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. A build script that loaded my shell profile
&lt;/h3&gt;

&lt;p&gt;Next failure was stranger. The native build resolver started walking a path that didn't exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...\app\Users\me\Side Projects\...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An absolute path had been glued onto a relative one. cargokit — the Rust/Flutter build glue — invokes&lt;br&gt;
a helper like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cmake"&gt;&lt;code&gt;powershell -ExecutionPolicy Bypass -File resolve_symlinks.ps1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;-NoProfile&lt;/code&gt;. So PowerShell loaded &lt;em&gt;my&lt;/em&gt; profile first. My profile has prompt and icon modules in it&lt;br&gt;
that emit errors on load in a non-interactive host. Those errors landed in the stream the CMake step&lt;br&gt;
was reading, and the resolver parsed the wreckage as a path.&lt;/p&gt;

&lt;p&gt;Running the identical command with &lt;code&gt;-NoProfile&lt;/code&gt; returned the correct path instantly.&lt;/p&gt;

&lt;p&gt;I used that locally to get unblocked and then reverted it — it's a weakness in a vendored build tool,&lt;br&gt;
not part of my fix, and it has no business in a bug-fix diff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transferable lesson:&lt;/strong&gt; any script your build shells out to inherits your shell's personality. If a&lt;br&gt;
build works on CI and dies on your machine with output that looks &lt;em&gt;parsed wrong&lt;/em&gt; rather than&lt;br&gt;
&lt;em&gt;missing&lt;/em&gt;, suspect your own profile before you suspect the build.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. A build artifact that has to exist before the build
&lt;/h3&gt;

&lt;p&gt;Third one was mundane, and I mention it only because it cost me twenty minutes of confusion: the&lt;br&gt;
Windows CMake step installs a &lt;code&gt;.msix&lt;/code&gt; helper file that isn't tracked in the repo, because it's&lt;br&gt;
generated. The project's own release scripts and CI build it. Locally, nobody had. One &lt;code&gt;makeappx.exe&lt;br&gt;
pack&lt;/code&gt; and the build ran to completion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transferable lesson:&lt;/strong&gt; "works in CI" often means "CI runs a step that nobody wrote down as a&lt;br&gt;
prerequisite." Read the CI workflow as build documentation. It is usually the most accurate build&lt;br&gt;
documentation a project has.&lt;/p&gt;

&lt;p&gt;After all three: &lt;code&gt;√ Built build\windows\x64\runner\Release\localsend_app.exe&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Now reproduce it on main
&lt;/h2&gt;

&lt;p&gt;Same steps. Same test file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two processes. Two visible windows.&lt;/strong&gt; The first still holds 53317; the second can't bind and sits&lt;br&gt;
there as a duplicate. Fifteen seconds later, still both.&lt;/p&gt;

&lt;p&gt;The bug the reporter described, on the current branch, deterministically.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two defects, one request
&lt;/h2&gt;

&lt;p&gt;Here's the mechanism. Explorer's "Send to" entry is a plain shortcut to the executable, with the&lt;br&gt;
selected file paths appended as arguments. There is no OS-level single-instance lock. The only thing&lt;br&gt;
standing between you and a second app is a small piece of startup code: post to the running instance's&lt;br&gt;
internal &lt;code&gt;show&lt;/code&gt; endpoint on loopback, and if it answers 200, exit.&lt;/p&gt;

&lt;p&gt;That request was failing for two independent reasons, either of which is sufficient on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defect one: it asked for the wrong URL.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The URL was built by passing a protocol version through a route builder. The version passed was a&lt;br&gt;
constant &lt;code&gt;'1.0'&lt;/code&gt;, and the route builder maps &lt;code&gt;'1.0'&lt;/code&gt; to the v1 path. The Rust server registers exactly&lt;br&gt;
one show route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nn"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/api/localsend/v2/show"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no v1 route in that server at all. So: 404, not 200, second instance carries on.&lt;/p&gt;

&lt;p&gt;The conceptual error underneath is the interesting bit. Protocol-version negotiation exists so you can&lt;br&gt;
talk to &lt;em&gt;someone else's&lt;/em&gt; device running &lt;em&gt;some other&lt;/em&gt; version. This endpoint talks to another copy of&lt;br&gt;
the same binary on the same machine. There is nothing to negotiate. Feeding it through the negotiation&lt;br&gt;
helper was a category mistake that happened to be harmless for as long as both paths were served.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defect two: it never proved who it was.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;LocalSend uses TLS with per-device self-signed certificates, and the server requires a &lt;em&gt;client&lt;/em&gt;&lt;br&gt;
certificate whenever it isn't also serving browser pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;mandatory_client_auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app_state&lt;/span&gt;&lt;span class="py"&gt;.web&lt;/span&gt;&lt;span class="nf"&gt;.is_none&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;app_state&lt;/span&gt;&lt;span class="py"&gt;.web_upload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The startup code used a bare &lt;code&gt;HttpClient&lt;/code&gt; with a &lt;code&gt;badCertificateCallback&lt;/code&gt; that returns &lt;code&gt;true&lt;/code&gt;. That&lt;br&gt;
callback is a very common source of false confidence: it overrides validation of the &lt;strong&gt;server's&lt;/strong&gt;&lt;br&gt;
certificate. It does nothing whatsoever about the server demanding one from &lt;strong&gt;you&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I put a standalone probe against the real Rust server to watch both halves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;no client cert       -&amp;gt; HttpException: TLSV1_ALERT_CERTIFICATE_REQUIRED (ssl/tls_record.cc:486) error 268436572
client cert, v2 path -&amp;gt; 200, and the server emits RsServerEvent.show_(args: [a.txt])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handshake is refused before the request is ever sent. The URL never even gets a chance to be&lt;br&gt;
wrong.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;git blame&lt;/code&gt; is not an accusation, it's a timeline
&lt;/h2&gt;

&lt;p&gt;I wanted to know how two defects landed on one request without anyone noticing, because "how did this&lt;br&gt;
survive review" usually explains the bug better than the bug does.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2026-07-14&lt;/strong&gt;, a commit that removes an HTTP dependency swaps
&lt;code&gt;createRhttpClient(timeout, securityContext)&lt;/code&gt; for a bare &lt;code&gt;HttpClient()&lt;/code&gt;. The certificate is dropped
right here. But the server at that moment was still the Dart one — and the Dart server &lt;em&gt;didn't ask&lt;/em&gt;
for client certificates, and served both v1 and v2. Nothing broke. The defect went latent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2026-07-15&lt;/strong&gt;, one day later, an unrelated-looking commit moves the show endpoint into the Rust
server. That server serves v2 only, and it demands a client certificate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both defects went live in the second commit, and neither commit is wrong when you read it on its own.&lt;br&gt;
The first is a faithful dependency swap. The second is a clean feature move. The bug lives in the&lt;br&gt;
space between them, which is exactly the kind of bug that code review is structurally bad at catching&lt;br&gt;
— because review looks at diffs, and no diff contains it.&lt;/p&gt;

&lt;p&gt;That's also why the shipping release is fine: 1.17.0 predates both.&lt;/p&gt;
&lt;h2&gt;
  
  
  The amplifier
&lt;/h2&gt;

&lt;p&gt;None of this had to be a mystery, because the startup code looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;force:&lt;/span&gt; &lt;span class="kc"&gt;true&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;A bare &lt;code&gt;catch (_)&lt;/code&gt;. Every failure mode above — the 404, the refused handshake — was swallowed here.&lt;br&gt;
The user gets a duplicate window and a port error, with nothing anywhere saying &lt;em&gt;why&lt;/em&gt; the handover&lt;br&gt;
didn't happen.&lt;/p&gt;

&lt;p&gt;I'd argue the empty catch is the more expensive defect. The other two are a wrong path and a missing&lt;br&gt;
certificate; they'd have been ten-minute fixes if anything had said "the handover was refused."&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Small, and mostly about putting the request somewhere it can be tested:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new &lt;code&gt;notifyRunningInstance(...)&lt;/code&gt; helper in the package that already owns the route definitions and
the stored certificate.&lt;/li&gt;
&lt;li&gt;It targets &lt;code&gt;ApiRoute.show.v2&lt;/code&gt; &lt;strong&gt;directly&lt;/strong&gt;, with a comment explaining that this endpoint is internal
and must not be version-negotiated. The next person who reaches for the negotiation helper should
hit a wall.&lt;/li&gt;
&lt;li&gt;It hands the client the device's own certificate from the persisted security context.&lt;/li&gt;
&lt;li&gt;Timeouts unchanged — 100 ms connect, 500 ms response. This runs on every app start, and a startup
path is not the place to get generous with waiting.&lt;/li&gt;
&lt;li&gt;The empty catch becomes outcome-aware. "Nothing is listening" is the normal cold-start case and
logs at &lt;code&gt;fine&lt;/code&gt;, i.e. silently. A &lt;strong&gt;refusal&lt;/strong&gt; or a &lt;strong&gt;failed handshake&lt;/strong&gt; logs at &lt;code&gt;warning&lt;/code&gt;, because
both mean something answered on that port and then denied the handover. That is never normal, and
it's exactly this bug's signature.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Net effect on the startup file: +10 / −32.&lt;/p&gt;

&lt;p&gt;One detail I got wrong on my own first pass, caught on a second read: I'd added a &lt;code&gt;drain()&lt;/code&gt; on the&lt;br&gt;
response to avoid leaving a socket half-read, with &lt;strong&gt;no timeout on it&lt;/strong&gt;. The response timeout only&lt;br&gt;
wrapped the request. So anything squatting on port 53317 that sends a status line and then stalls&lt;br&gt;
would have hung app startup forever. Low probability, unacceptable failure mode — this code runs every&lt;br&gt;
time the app opens. Now the status code is captured first, the drain is bounded, and a body that never&lt;br&gt;
arrives can't turn a genuine 200 into a duplicate instance.&lt;/p&gt;

&lt;p&gt;Reviewing your own patch as if someone else wrote it is worth the twenty minutes. That one was mine&lt;br&gt;
and I'd have shipped it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test, and what it honestly can't do
&lt;/h2&gt;

&lt;p&gt;Three cases: the handover succeeds and forwards the arguments; a rejected token doesn't exit; nothing&lt;br&gt;
listening doesn't exit. The stub server encodes the real server's contract as an ordered switch:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;no client certificate&lt;/td&gt;
&lt;td&gt;401&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;path isn't &lt;code&gt;/api/localsend/v2/show&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;token mismatch&lt;/td&gt;
&lt;td&gt;403&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;otherwise&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Which means I could put back each defect &lt;strong&gt;on its own&lt;/strong&gt; and watch the suite fail for it — wrong path&lt;br&gt;
alone: fail. Missing certificate alone: fail. That matters more than a passing test does. A regression&lt;br&gt;
test that has never been seen to fail is a decoration.&lt;/p&gt;

&lt;p&gt;What it can't do: the stub is a Dart server, not the real Rust one. I tried running the real server&lt;br&gt;
in-process under the test runner, and the Rust TLS stack and the client's BoringSSL deadlocked hard&lt;br&gt;
enough to take the runner down with them. So the real-server verification happens out of band, and the&lt;br&gt;
test guards the contract rather than the implementation.&lt;/p&gt;

&lt;p&gt;And one more caveat I put in the PR rather than hiding: the test mints its throwaway identity through&lt;br&gt;
the Rust bridge, so it self-skips when the Rust library isn't built — and the project's CI doesn't&lt;br&gt;
build it. &lt;strong&gt;These tests will skip on CI.&lt;/strong&gt; An existing test in the same package already skips for the&lt;br&gt;
same reason, so it's a known shape there. The alternative was committing a static certificate and&lt;br&gt;
private key to the repo to remove the Rust dependency, and I'd rather ship a visible skip than an&lt;br&gt;
invisible security smell. I said so in the PR and offered to do it whichever way the maintainers&lt;br&gt;
prefer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving it, from outside the process
&lt;/h2&gt;

&lt;p&gt;Back to why I picked a bug with a port conflict in it. The final check doesn't involve my opinion.&lt;/p&gt;

&lt;p&gt;I built &lt;code&gt;main&lt;/code&gt; twice from the same tree — once with the startup file at upstream, once with the fix —&lt;br&gt;
pointed the real Explorer Send-To shortcut at each build in turn, and invoked it through&lt;br&gt;
&lt;code&gt;ShellExecute&lt;/code&gt; with a 27-byte test file, which is exactly how Explorer launches a Send-To target.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Build&lt;/th&gt;
&lt;th&gt;Processes&lt;/th&gt;
&lt;th&gt;Windows&lt;/th&gt;
&lt;th&gt;Port 53317&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;unfixed &lt;code&gt;main&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;first PID holds it, second can't bind&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;with the fix&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;never contested&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sustained across fifteen seconds of sampling, not a single snapshot.&lt;/p&gt;

&lt;p&gt;And the piece that makes it a fix rather than a workaround: the surviving instance came to the&lt;br&gt;
foreground on its Send page showing &lt;strong&gt;&lt;code&gt;Files: 1, Size: 27 B&lt;/code&gt;&lt;/strong&gt;. The arguments reached the running&lt;br&gt;
instance. I wasn't just suppressing the second window — I was doing the thing the second window was&lt;br&gt;
supposed to have done.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is true right now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;On current &lt;code&gt;main&lt;/code&gt;, the flow reproduces deterministically, for two concrete reasons in two named
files.&lt;/li&gt;
&lt;li&gt;The fix is three files, one of which is a test, and it passes format, analyze and test in both
affected packages.&lt;/li&gt;
&lt;li&gt;The end-to-end Windows behaviour is measurably fixed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The PR is open and awaiting review: &lt;a href="https://github.com/localsend/localsend/pull/3246" rel="noopener noreferrer"&gt;https://github.com/localsend/localsend/pull/3246&lt;/a&gt;.&lt;/strong&gt; Not merged. Not approved. The
maintainers may want a different shape entirely, and that's their call.&lt;/li&gt;
&lt;li&gt;I could &lt;strong&gt;not&lt;/strong&gt; reproduce the original reporter's symptom on the shipping 1.17.0 build. I said so in
the PR and left the issue open rather than auto-closing it. What I fixed is real, and it's the same
user-visible behaviour — but claiming I'd solved someone else's 2026-07-03 report on a February 2025
binary would be a story, not a result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most useful thing I did on this bug was the first thing: install the release and try to reproduce&lt;br&gt;
it, and then take "it doesn't reproduce" seriously instead of treating it as an inconvenience. That&lt;br&gt;
single negative result is what turned a guess into a diagnosis.&lt;/p&gt;

</description>
      <category>bugsmash</category>
      <category>devchallenge</category>
      <category>flutter</category>
      <category>rust</category>
    </item>
    <item>
      <title>One Extra Slash: Hunting a Windows Path Bug Down to a Single Formatter</title>
      <dc:creator>Anthony</dc:creator>
      <pubDate>Thu, 06 Aug 2026 20:07:18 +0000</pubDate>
      <link>https://dev.to/antheducation/one-extra-slash-hunting-a-windows-path-bug-down-to-a-single-formatter-o79</link>
      <guid>https://dev.to/antheducation/one-extra-slash-hunting-a-windows-path-bug-down-to-a-single-formatter-o79</guid>
      <description>&lt;p&gt;&lt;em&gt;This is my entry for DEV's Summer Bug Smash — Smash Stories.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Not every good bug is an epic. Some of them are one character long, and the interesting part is entirely in the hunt.&lt;/p&gt;

&lt;p&gt;I was consuming the JSON output of a developer CLI — machine-readable mode, the structured projection you're supposed to build tooling against. My code was doing something completely ordinary: taking a file path out of that JSON and comparing it against a path I already had.&lt;/p&gt;

&lt;p&gt;The comparison kept failing on paths that were obviously, visibly identical.&lt;/p&gt;

&lt;p&gt;They weren't identical. The JSON contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C://Users/me/project/src/index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two separators after the drive letter. One too many.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is worse than it looks
&lt;/h2&gt;

&lt;p&gt;Your first reaction to a doubled slash is probably "so what?" — and for a lot of purposes, you're right. Hand &lt;code&gt;C://Users/me/file.ts&lt;/code&gt; to most filesystem APIs on Windows and it will happily open the file. The OS and most path libraries collapse redundant separators. Nothing crashes. Nothing errors. You can go a long time without noticing.&lt;/p&gt;

&lt;p&gt;The damage shows up the moment anything treats a path as a &lt;strong&gt;string&lt;/strong&gt; rather than as a &lt;strong&gt;path&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;String equality between two paths silently returns false&lt;/li&gt;
&lt;li&gt;Anything used as a map key or cache key produces a duplicate entry for the same physical file&lt;/li&gt;
&lt;li&gt;Deduplication logic keeps both variants&lt;/li&gt;
&lt;li&gt;Diffs and logs show two "different" files that are the same file&lt;/li&gt;
&lt;li&gt;Any downstream consumer that splits on the separator gets a phantom empty segment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the real cost. It doesn't fail loudly at the source. It fails quietly, later, in whatever unlucky code is doing the comparing — which in this case was mine. This is a bug that spends its whole life framing someone else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step one: is it actually them, or is it me?
&lt;/h2&gt;

&lt;p&gt;This is the step I think people skip too often, and it's the one that earns you the right to file the report.&lt;/p&gt;

&lt;p&gt;My first assumption was that I had mangled the path myself. Something in my own pipeline joining, normalising, or concatenating badly. So before looking anywhere else, I went and checked the raw bytes coming out of the CLI, before my code touched them at all.&lt;/p&gt;

&lt;p&gt;The doubled separator was already there. It arrived that way.&lt;/p&gt;

&lt;p&gt;That reframed the problem from "fix my code" to "characterise someone else's bug" — and those need very different work. A bug report that says "your tool is broken" is worth almost nothing. A bug report that says "your tool is broken &lt;em&gt;here specifically, and not there&lt;/em&gt;" is worth a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step two: scope it against its siblings
&lt;/h2&gt;

&lt;p&gt;The key question was: &lt;strong&gt;how wide is this?&lt;/strong&gt; Is every path this tool emits affected, or just one?&lt;/p&gt;

&lt;p&gt;So I ran the sibling commands — the other subcommands that emit paths in the same structured mode — and compared their output against the one that was misbehaving.&lt;/p&gt;

&lt;p&gt;The result was clean and specific: the human-readable output was correct. The other commands' structured output was correct. &lt;strong&gt;Only one particular projection in one particular command emitted the doubled separator.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That single fact does enormous work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It rules out the underlying path library. If the shared library were broken, every command would be wrong.&lt;/li&gt;
&lt;li&gt;It rules out platform-level normalisation and my shell environment, for the same reason.&lt;/li&gt;
&lt;li&gt;It localises the defect to one formatting site — one place where a path gets assembled for output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this step I hadn't seen a line of their source, but I could say with confidence that this was a local formatting bug in a single code path, not a systemic one. That's a genuinely useful thing to be able to hand a maintainer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step three: the mechanism
&lt;/h2&gt;

&lt;p&gt;The shape of this bug is a classic, and once you've seen it once you'll recognise it forever.&lt;/p&gt;

&lt;p&gt;Somewhere, a path is being assembled roughly like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;separator&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;relativePart&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On POSIX that's usually harmless-looking, because a root like &lt;code&gt;/home/me&lt;/code&gt; doesn't end in a separator. On Windows, a drive root is different: &lt;code&gt;C:/&lt;/code&gt; (or &lt;code&gt;C:\&lt;/code&gt;) &lt;strong&gt;already terminates with a separator&lt;/strong&gt;. It has to — &lt;code&gt;C:&lt;/code&gt; alone means "the current directory on drive C", which is a different thing entirely. The trailing separator isn't decoration, it's semantic.&lt;/p&gt;

&lt;p&gt;So the naive join appends a separator to a string that already ends in one, and you get &lt;code&gt;C://&lt;/code&gt;. The bug only manifests when the root is a drive root, which is why it hides so well: any path assembled relative to a normal subdirectory looks perfectly fine, and only the drive-root case exposes it.&lt;/p&gt;

&lt;p&gt;This is why the correct tool is the platform's path-join function rather than string concatenation. Join functions exist precisely to know that a root may already carry its separator. Every time someone hand-rolls a join with &lt;code&gt;+&lt;/code&gt;, this bug is one Windows user away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step four: report it properly, and bring the test
&lt;/h2&gt;

&lt;p&gt;I filed it upstream with the things I'd want if I were the maintainer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A minimal reproduction&lt;/strong&gt; — the exact command, on the exact platform, with the exact offending output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The scoping evidence&lt;/strong&gt; — that sibling commands and the human-readable output are all correct, so the fault is localised to that one projection rather than the shared path handling. This is the part that saves a maintainer an hour of hunting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The proposed mechanism&lt;/strong&gt; — drive-root joins double the separator, with the reasoning above.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A suggested regression test.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one matters more than people give it credit for. The test I proposed asserts that the emitted path for a file at a &lt;strong&gt;drive root&lt;/strong&gt; contains no doubled separator — specifically the drive-root case, not a generic path case.&lt;/p&gt;

&lt;p&gt;That specificity is the whole point. A test using a normal nested path passes happily against the broken code and protects nothing. The bug lives &lt;em&gt;only&lt;/em&gt; at the drive root, so the test has to live at the drive root too. A regression test that doesn't reproduce the original bug is decoration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took from it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prove it isn't yours before you file.&lt;/strong&gt; Checking the raw output before my own code touched it took two minutes and completely changed what I was doing. Half of "their bug" reports are ours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope before you speculate.&lt;/strong&gt; Running the sibling commands was the highest-value five minutes of the whole hunt. It converted "something's wrong with this tool" into "this one formatter is wrong and the shared library is fine" — without reading their source at all. You can characterise a bug from the outside far more precisely than most people attempt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Windows drive roots are a genuine edge case, not a nuisance.&lt;/strong&gt; &lt;code&gt;C:/&lt;/code&gt; ending in a separator is correct and load-bearing. Any hand-rolled path concatenation that assumes roots don't end in separators is already broken; it just hasn't met a Windows user yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ship the test with the report.&lt;/strong&gt; A regression test that fails on the current code and passes after the fix is the single most useful artifact you can attach to a bug report — and it forces you to state precisely what the correct behaviour is, which is a good discipline even when nobody else ever reads it.&lt;/p&gt;

&lt;p&gt;One extra slash. Nothing crashed, nothing errored, and it still cost real time — because the failure surfaced in a completely different codebase from the one that caused it.&lt;/p&gt;

</description>
      <category>bugsmash</category>
      <category>devchallenge</category>
      <category>windows</category>
      <category>cli</category>
    </item>
    <item>
      <title>The Synth Sounded Fine When Nobody Was Listening</title>
      <dc:creator>Anthony</dc:creator>
      <pubDate>Thu, 06 Aug 2026 20:07:11 +0000</pubDate>
      <link>https://dev.to/antheducation/the-synth-sounded-fine-when-nobody-was-listening-50l</link>
      <guid>https://dev.to/antheducation/the-synth-sounded-fine-when-nobody-was-listening-50l</guid>
      <description>&lt;p&gt;&lt;em&gt;This is my entry for DEV's Summer Bug Smash — Smash Stories.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is a specific kind of bug that makes you doubt your own ears.&lt;/p&gt;

&lt;p&gt;I had a browser-based audio engine that played fine in export and sounded broken in real time. Hit play, and the output was thin, muffled, strangled — like someone had thrown a blanket over the speakers. Hit "render to file" on the &lt;em&gt;exact same project data&lt;/em&gt;, and the resulting audio was perfect. Full, rich, every note sustaining exactly as written.&lt;/p&gt;

&lt;p&gt;Same notes. Same instruments. Same code, as far as I knew. One path sounded right and one path sounded wrong, and I could reproduce both on demand, forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this was so hard to trust
&lt;/h2&gt;

&lt;p&gt;The maddening part of an audio bug is that "muffled" isn't an error message. Nothing threw. Nothing logged. There was no stack trace, no failed assertion, no red text anywhere. The app was, by every measure I had instrumented, working.&lt;/p&gt;

&lt;p&gt;My first instinct was the obvious one: it's a filter. Something in the live path has a lowpass on it that the offline path doesn't. So I went hunting for a rogue filter node, a wrong cutoff default, a gain stage set too low. I found nothing, because there was nothing to find.&lt;/p&gt;

&lt;p&gt;My second instinct was that it was a &lt;em&gt;performance&lt;/em&gt; problem — that live playback was simply too expensive and the audio thread was starving. That felt plausible. It was also wrong, and chasing it cost me real time. CPU was fine. The audio thread was not underrunning.&lt;/p&gt;

&lt;p&gt;The thing that finally broke the case open was reframing the question. I stopped asking &lt;strong&gt;"what is different about the sound?"&lt;/strong&gt; and started asking &lt;strong&gt;"what is structurally different about the two code paths?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because in Web Audio, offline rendering and live playback are genuinely different machines. An offline render runs as fast as it can through a deterministic timeline. Live playback runs on a scheduler — a loop that wakes up, looks a short distance into the future, and schedules whatever notes fall inside that window. Offline had no scheduler at all. Live did.&lt;/p&gt;

&lt;p&gt;That was the only meaningful structural difference. So the scheduler became the suspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Counting the bodies
&lt;/h2&gt;

&lt;p&gt;Rather than reading the scheduler and trying to reason about it, I instrumented it. I counted every voice it created and — crucially — every voice it &lt;em&gt;destroyed&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The number came back at roughly &lt;strong&gt;6,636 audio nodes killed in a single short run.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That was the moment the bug stopped being mysterious and became almost funny. I wasn't listening to muffled audio. I was listening to audio that was being &lt;strong&gt;murdered and resurrected roughly sixty times a second&lt;/strong&gt;, and what reached my ears was only ever the first few milliseconds of each note's life, over and over.&lt;/p&gt;

&lt;p&gt;The sound wasn't filtered. It was &lt;em&gt;amputated&lt;/em&gt;. Every note was being cut off during its attack, before the envelope had any chance to develop into an actual tone. That is exactly what "thin and strangled" sounds like, and once I knew it, I couldn't un-hear it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual root cause
&lt;/h2&gt;

&lt;p&gt;The scheduler had a lookahead window of &lt;strong&gt;0.28 seconds&lt;/strong&gt;. Every tick, it scheduled all voices falling inside the next 0.28s of the timeline. Standard, sensible design.&lt;/p&gt;

&lt;p&gt;It also had a seek detector. This is a normal and necessary thing to have: if the user drags the playhead backwards, every note you already scheduled into the future is now wrong, so you cancel everything and reschedule from the new position. Also standard, also sensible.&lt;/p&gt;

&lt;p&gt;The bug lived in the relationship between those two features.&lt;/p&gt;

&lt;p&gt;The seek detector decided "the user has seeked backwards" by comparing the current transport position against the position it last saw — using a &lt;strong&gt;threshold that was smaller than the lookahead window itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Read that again, because it's the whole bug.&lt;/p&gt;

&lt;p&gt;The scheduler's own job is to run &lt;em&gt;ahead&lt;/em&gt; of the playhead. It deliberately pushes its working position up to 0.28s into the future. So on the next tick, the actual playhead is naturally "behind" where the scheduler had last been working — by an amount governed by the size of the lookahead window.&lt;/p&gt;

&lt;p&gt;To the seek detector, that ordinary, intended, by-design gap was indistinguishable from a user dragging the playhead backwards.&lt;/p&gt;

&lt;p&gt;So it fired. It cancelled every scheduled voice and rebuilt them from scratch. Then the scheduler ran ahead again — because that is its entire purpose — which re-created the same backwards gap, which tripped the detector again, which cancelled everything again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The scheduler was seeing its own lookahead as a seek and panicking about it, on every single frame.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two features that were each individually correct, wired together into a perfect self-sustaining loop. Neither one was buggy on its own. The bug existed only in the space between them.&lt;/p&gt;

&lt;p&gt;And it explained the offline path perfectly: the offline render never ran the live scheduler, so the seek detector never existed, so nothing ever got cancelled, so every note lived its full life. The "working" path wasn't working because it was better. It was working because it never touched the broken code at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;The fix was to make the backward-seek threshold larger than the lookahead window, so that the scheduler's own intended forward progress could never be mistaken for a user seek.&lt;/p&gt;

&lt;p&gt;One constant. &lt;strong&gt;Two bytes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the entire patch. Six thousand dead audio nodes per run, weeks of a product sounding subtly wrong, an entire wrong theory about CPU starvation — all resolved by making one number bigger than another number.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually took away from this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The two-path comparison is the most underrated debugging tool there is.&lt;/strong&gt; I burned time hunting for a difference in the &lt;em&gt;audio graph&lt;/em&gt; when the real difference was in the &lt;em&gt;control flow&lt;/em&gt;. Whenever you have one path that works and one that doesn't, the fastest question is not "what's wrong with the broken one" — it's "what does the broken one do that the working one never touches?" That question pointed straight at the scheduler, and the scheduler was the answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Count things.&lt;/strong&gt; I could have read that scheduler top to bottom and still argued myself into believing it was correct, because every individual piece of it &lt;em&gt;was&lt;/em&gt; correct. I couldn't argue with 6,636 destroyed nodes. A counter turned an unfalsifiable "it sounds bad" into a hard number, and the hard number named the culprit immediately. When a bug has no error message, instrumentation is how you manufacture one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptoms lie about their category.&lt;/strong&gt; This presented as an &lt;em&gt;audio quality&lt;/em&gt; problem. Every instinct said filter, gain, DSP. It was a &lt;em&gt;timing&lt;/em&gt; problem wearing an audio costume. I've started treating my first instinct about a bug's category as a hypothesis to disprove rather than a place to start digging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bugs love the seams.&lt;/strong&gt; The most expensive bugs I've hit aren't inside a function. They're in the interaction between two functions that are both fine. A lookahead window and a seek threshold are each trivially correct in isolation. Nobody reviewing either one alone would blink. The defect only exists when you hold them up together, and no unit test that tests them separately will ever catch it.&lt;/p&gt;

&lt;p&gt;The synth sounds right now. It sounded right in export the whole time — it just needed everyone to stop killing it sixty times a second.&lt;/p&gt;

</description>
      <category>bugsmash</category>
      <category>devchallenge</category>
      <category>webaudio</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
