<?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: fabperso</title>
    <description>The latest articles on DEV Community by fabperso (@fabperso).</description>
    <link>https://dev.to/fabperso</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%2F4051733%2F4be95974-f048-46cc-8f68-74ffca639ebd.png</url>
      <title>DEV Community: fabperso</title>
      <link>https://dev.to/fabperso</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fabperso"/>
    <language>en</language>
    <item>
      <title>Three bugs my AI agents couldn't fix</title>
      <dc:creator>fabperso</dc:creator>
      <pubDate>Tue, 28 Jul 2026 16:19:08 +0000</pubDate>
      <link>https://dev.to/fabperso/three-bugs-my-ai-agents-couldnt-fix-13bn</link>
      <guid>https://dev.to/fabperso/three-bugs-my-ai-agents-couldnt-fix-13bn</guid>
      <description>&lt;p&gt;I spent the last few weeks building &lt;a href="https://github.com/fabperso/wimux" rel="noopener noreferrer"&gt;wimux&lt;/a&gt;,&lt;br&gt;
a terminal multiplexer for Windows written in Rust. &lt;code&gt;tmux&lt;/code&gt; and &lt;code&gt;zellij&lt;/code&gt; are&lt;br&gt;
Unix-first — on Windows they only really live inside WSL, detached from the&lt;br&gt;
native shell. I wanted persistent PowerShell sessions, so I wrote them.&lt;/p&gt;

&lt;p&gt;Most of the code was written by AI agents. My commits say so explicitly&lt;br&gt;
(&lt;code&gt;Co-Authored-By:&lt;/code&gt; trailers), and I'd rather lead with that than have someone&lt;br&gt;
discover it. What I want to write about isn't the code the agents produced —&lt;br&gt;
it's the three bugs they &lt;strong&gt;couldn't&lt;/strong&gt; fix, because those turned out to be the&lt;br&gt;
interesting part.&lt;/p&gt;

&lt;p&gt;A quick sketch of the thing, so the bugs make sense: a detached per-user daemon&lt;br&gt;
owns the sessions, drives child processes through &lt;strong&gt;ConPTY&lt;/strong&gt; (&lt;code&gt;portable-pty&lt;/code&gt;),&lt;br&gt;
and emulates the terminals server-side. Clients — a TUI, a Tauri v2 GUI running&lt;br&gt;
xterm.js, and a scriptable CLI — attach over a Windows named pipe with a&lt;br&gt;
&lt;code&gt;postcard&lt;/code&gt; binary protocol. So: a Rust daemon, a WebView, and a PTY, three&lt;br&gt;
layers that each have their own idea of what the screen looks like.&lt;/p&gt;


&lt;h2&gt;
  
  
  Bug 1: the corruption that wasn't where everyone looked
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; split a pane while a full-screen TUI app was running, and the&lt;br&gt;
display filled with orphaned characters — box-drawing glyphs stranded from a&lt;br&gt;
narrower render.&lt;/p&gt;

&lt;p&gt;Everything pointed at the client. xterm.js is the thing drawing pixels; the&lt;br&gt;
GUI reparents DOM nodes when the layout changes; re-attaching to the session&lt;br&gt;
made the corruption disappear. Obvious client bug.&lt;/p&gt;

&lt;p&gt;We tried four fixes, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stop reparenting the DOM — position panes absolutely instead. &lt;em&gt;Still broken.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Have the daemon push a fresh snapshot after each resize. &lt;em&gt;Still broken.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Recreate the xterm instance on structural changes. &lt;em&gt;Still broken.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Debounce the reattach so it happens after the layout settles. &lt;em&gt;Still broken.&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Four plausible fixes, four failures. Each one was a reasonable theory, and each&lt;br&gt;
one was aimed at the client.&lt;/p&gt;

&lt;p&gt;The move that cracked it took thirty seconds: I captured the terminal grid&lt;br&gt;
&lt;strong&gt;from the server&lt;/strong&gt;, using the daemon's own view of the pane — the one that has&lt;br&gt;
never heard of xterm.js.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wimux agent capture &lt;span class="nt"&gt;-t&lt;/span&gt; 0 &lt;span class="nt"&gt;-p&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The orphaned glyphs were there too.&lt;/p&gt;

&lt;p&gt;That single output eliminated the entire client. The corruption existed before&lt;br&gt;
a single pixel was drawn. The real cause was upstream of everything we'd been&lt;br&gt;
touching: splitting a pane sends a &lt;strong&gt;burst of intermediate sizes&lt;/strong&gt; to the PTY.&lt;br&gt;
The app inside redraws at each one, its incremental redraws don't clear what&lt;br&gt;
they no longer cover, and the leftovers stay in the grid — in the server's grid.&lt;/p&gt;

&lt;p&gt;The fix was three lines: debounce resize events so the PTY only ever sees the&lt;br&gt;
final, settled size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson isn't "debounce your resizes."&lt;/strong&gt; It's that four consecutive fixes&lt;br&gt;
failing is data. It means the assumption underneath all four is wrong — and no&lt;br&gt;
amount of new fixes at the same layer will help. Go get an observation from a&lt;br&gt;
&lt;em&gt;different&lt;/em&gt; layer instead.&lt;/p&gt;


&lt;h2&gt;
  
  
  Bug 2: the keystroke that vanished
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; click a pane, type — the first keypress doesn't appear, and the&lt;br&gt;
machine beeps. Press again and it works.&lt;/p&gt;

&lt;p&gt;This one resisted for days, partly because every reasonable theory was wrong.&lt;br&gt;
Here's what I eliminated, and how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The click→focus mechanics.&lt;/strong&gt; I rebuilt the exact handler in a bare Chromium
page — same xterm version, same &lt;code&gt;mousedown&lt;/code&gt; → &lt;code&gt;term.focus()&lt;/code&gt;. The first
keypress went through, every time. &lt;em&gt;Not it.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A layout re-render stealing focus.&lt;/strong&gt; Reading the code: on an unchanged
layout it's a no-op, and the refocus is guarded. &lt;em&gt;Not it.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Another process stealing the foreground.&lt;/strong&gt; I logged &lt;code&gt;GetForegroundWindow&lt;/code&gt;
and &lt;code&gt;GetGUIThreadInfo&lt;/code&gt; while typing. Focus never moved. &lt;em&gt;Not it.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus churn on every keystroke.&lt;/strong&gt; An in-page overlay logging
&lt;code&gt;document.hasFocus()&lt;/code&gt; showed all keystrokes landing on the terminal's
textarea. The blur/focus events I'd blamed were me alt-tabbing. &lt;em&gt;Not it.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal focus reporting (DECSET 1004)&lt;/strong&gt; — a beautiful theory: the terminal
emits &lt;code&gt;ESC [ I&lt;/code&gt; on focus, PSReadLine chokes on it, beeps, and eats the next
character. It explains every symptom. I enabled the mode and watched what
xterm actually emitted: nothing. ConPTY had swallowed the sequence. &lt;em&gt;Not it.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five theories, five refutations, all backed by an observation rather than an&lt;br&gt;
argument. What finally worked was building a &lt;strong&gt;reproduction harness&lt;/strong&gt;: a script&lt;br&gt;
that clicks a pane and sends a keystroke after a delay, sweeping the delay from&lt;br&gt;
0 ms to 150 ms, dozens of times. A human can't hit a 50 ms window on purpose. A&lt;br&gt;
script does it forty times in a row.&lt;/p&gt;

&lt;p&gt;Result: &lt;strong&gt;zero losses.&lt;/strong&gt; Clicking &lt;em&gt;inside&lt;/em&gt; a pane was never the problem.&lt;/p&gt;

&lt;p&gt;So I pointed the harness at the sidebar instead — clicking a session entry to&lt;br&gt;
switch workspaces. &lt;strong&gt;Four out of four keystrokes lost&lt;/strong&gt;, with a log that made&lt;br&gt;
the mechanism unmistakable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MD  pane=-  target=SPAN.name  active=TEXTAREA.xterm-helper-textarea
== BLUR ==                          the terminal loses focus
KEY "k"  active=BODY  target=BODY   the keystroke lands on &amp;lt;body&amp;gt;
== FOCUS ==                         focus returns ~310 ms later
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sidebar entries are plain &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s. They aren't focusable, so clicking one&lt;br&gt;
drops document focus to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. Then two delays stack: a 200 ms timer that&lt;br&gt;
disambiguates double-click-to-rename, plus a 300–450 ms server round-trip to&lt;br&gt;
rebuild the panes. For &lt;strong&gt;half a second&lt;/strong&gt;, keystrokes go nowhere — and Windows&lt;br&gt;
beeps at a key sent to a non-editable element.&lt;/p&gt;

&lt;p&gt;The fix follows from the diagnosis: in a terminal app, a keystroke with no&lt;br&gt;
focused field belongs to the active pane. It gets delivered there, and if a&lt;br&gt;
switch is in flight — the target pane doesn't exist yet — it's buffered and&lt;br&gt;
replayed into the pane you switched to.&lt;/p&gt;

&lt;p&gt;Verified the same way it was found: 0/4 keystrokes delivered before, 4/4 after,&lt;br&gt;
each in the correct session's pane.&lt;/p&gt;


&lt;h2&gt;
  
  
  Bug 3: the lag that was an accounting problem
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; with several panes open, typing lagged badly.&lt;/p&gt;

&lt;p&gt;This one was quick, and it's the clearest example of a class of bug agents are&lt;br&gt;
bad at: nothing is &lt;em&gt;wrong&lt;/em&gt; anywhere. Every line of code is correct. The daemon&lt;br&gt;
read PTY output and emitted one IPC message per chunk. Each message crossed&lt;br&gt;
into the WebView and hit a single JavaScript thread. With several panes&lt;br&gt;
producing output, that thread spent its life in message dispatch instead of&lt;br&gt;
rendering.&lt;/p&gt;

&lt;p&gt;The fix was to drain what's already available and merge consecutive chunks from&lt;br&gt;
the same pane into one message, capped at 64 KiB.&lt;/p&gt;

&lt;p&gt;No bug was fixed. An accounting decision was changed. That's a category agents&lt;br&gt;
rarely reach for on their own, because there's no error to find.&lt;/p&gt;


&lt;h2&gt;
  
  
  The agents that reported success and wrote nothing
&lt;/h2&gt;

&lt;p&gt;Worth a mention, because it cost me an hour of confusion.&lt;/p&gt;

&lt;p&gt;I fanned a task out across several agents, each in its own isolated git&lt;br&gt;
worktree. All of them reported success in careful, structured prose. Not one&lt;br&gt;
had written a single file: in non-interactive mode they needed an explicit&lt;br&gt;
permission flag to touch the filesystem, and without it they narrated the work&lt;br&gt;
they would have done.&lt;/p&gt;

&lt;p&gt;What saved me was that the review step didn't read their reports — it ran&lt;br&gt;
&lt;code&gt;git status&lt;/code&gt; in each worktree and counted changed files. It said &lt;code&gt;0&lt;/code&gt;, and the&lt;br&gt;
&lt;code&gt;0&lt;/code&gt; was true.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust the artifact, never the report.&lt;/strong&gt; This is the single most useful habit&lt;br&gt;
I've taken from working this way.&lt;/p&gt;


&lt;h2&gt;
  
  
  What I'd tell you about building this way
&lt;/h2&gt;

&lt;p&gt;Agents produced a working multiplexer faster than I could have alone. They also&lt;br&gt;
produced four consecutive confident wrong fixes for the first bug, and would&lt;br&gt;
have produced a fifth if I'd asked.&lt;/p&gt;

&lt;p&gt;The thing they don't do on their own is &lt;strong&gt;stop and go get evidence&lt;/strong&gt;. They&lt;br&gt;
generate plausible next steps, and plausible is exactly the failure mode you&lt;br&gt;
have to defend against. Every one of these three bugs was solved by the same&lt;br&gt;
move: capture the state at a layer nobody was looking at.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bug 1: read the grid from the server instead of the browser.&lt;/li&gt;
&lt;li&gt;Bug 2: log the component boundaries to a file, then build a harness that
hammers the timing window a human can't hit.&lt;/li&gt;
&lt;li&gt;Bug 3: notice that nothing was broken, and count messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is exotic. It's ordinary debugging discipline — the kind that's&lt;br&gt;
easy to skip when something is handing you a confident answer every thirty&lt;br&gt;
seconds. If you take one thing from this: &lt;strong&gt;when three fixes in a row fail,&lt;br&gt;
stop fixing. Your shared assumption is the bug.&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;wimux is MIT-licensed and runs on Windows 10/11:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;cargo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wimux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wimux-server&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo, demo GIF and the full architecture write-up:&lt;br&gt;
&lt;a href="https://github.com/fabperso/wimux" rel="noopener noreferrer"&gt;github.com/fabperso/wimux&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions — especially about ConPTY, which was by far the&lt;br&gt;
messiest part.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cli</category>
      <category>programming</category>
      <category>rust</category>
    </item>
  </channel>
</rss>
