cd /news/developer-tools/three-bugs-my-ai-agents-couldn-t-fix · home topics developer-tools article
[ARTICLE · art-77275] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Three bugs my AI agents couldn't fix

A developer building wimux, a terminal multiplexer for Windows in Rust, found that AI agents could not fix three bugs caused by interactions between ConPTY, xterm.js, and the server-side grid. The bugs were resolved by debouncing resize events and building a reproduction harness to isolate focus issues, highlighting the need for cross-layer debugging.

read6 min views1 publishedJul 28, 2026

I spent the last few weeks building wimux,

a terminal multiplexer for Windows written in Rust. tmux

and zellij

are

Unix-first — on Windows they only really live inside WSL, detached from the

native shell. I wanted persistent PowerShell sessions, so I wrote them.

Most of the code was written by AI agents. My commits say so explicitly

(Co-Authored-By:

trailers), and I'd rather lead with that than have someone

discover it. What I want to write about isn't the code the agents produced —

it's the three bugs they couldn't fix, because those turned out to be the

interesting part.

A quick sketch of the thing, so the bugs make sense: a detached per-user daemon

owns the sessions, drives child processes through ConPTY (portable-pty

),

and emulates the terminals server-side. Clients — a TUI, a Tauri v2 GUI running

xterm.js, and a scriptable CLI — attach over a Windows named pipe with a

postcard

binary protocol. So: a Rust daemon, a WebView, and a PTY, three

layers that each have their own idea of what the screen looks like.

Symptom: split a pane while a full-screen TUI app was running, and the

display filled with orphaned characters — box-drawing glyphs stranded from a

narrower render.

Everything pointed at the client. xterm.js is the thing drawing pixels; the

GUI reparents DOM nodes when the layout changes; re-attaching to the session

made the corruption disappear. Obvious client bug.

We tried four fixes, in this order:

Four plausible fixes, four failures. Each one was a reasonable theory, and each

one was aimed at the client.

The move that cracked it took thirty seconds: I captured the terminal grid

from the server, using the daemon's own view of the pane — the one that has

never heard of xterm.js.

wimux agent capture -t 0 -p 2

The orphaned glyphs were there too.

That single output eliminated the entire client. The corruption existed before

a single pixel was drawn. The real cause was upstream of everything we'd been

touching: splitting a pane sends a burst of intermediate sizes to the PTY.

The app inside redraws at each one, its incremental redraws don't clear what

they no longer cover, and the leftovers stay in the grid — in the server's grid.

The fix was three lines: debounce resize events so the PTY only ever sees the

final, settled size.

The lesson isn't "debounce your resizes." It's that four consecutive fixes

failing is data. It means the assumption underneath all four is wrong — and no

amount of new fixes at the same layer will help. Go get an observation from a

different layer instead.

Symptom: click a pane, type — the first keypress doesn't appear, and the

machine beeps. Press again and it works.

This one resisted for days, partly because every reasonable theory was wrong.

Here's what I eliminated, and how:

mousedown

term.focus()

. The first keypress went through, every time. GetForegroundWindow

and GetGUIThreadInfo

while typing. Focus never moved. document.hasFocus()

showed all keystrokes landing on the terminal's textarea. The blur/focus events I'd blamed were me alt-tabbing. ESC [ I

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. Five theories, five refutations, all backed by an observation rather than an

argument. What finally worked was building a reproduction harness: a script

that clicks a pane and sends a keystroke after a delay, sweeping the delay from

0 ms to 150 ms, dozens of times. A human can't hit a 50 ms window on purpose. A

script does it forty times in a row.

Result: zero losses. Clicking inside a pane was never the problem.

So I pointed the harness at the sidebar instead — clicking a session entry to

switch workspaces. Four out of four keystrokes lost, with a log that made

the mechanism unmistakable:

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 <body>
== FOCUS ==                         focus returns ~310 ms later

The sidebar entries are plain <div>

s. They aren't focusable, so clicking one

drops document focus to <body>

. Then two delays stack: a 200 ms timer that

disambiguates double-click-to-rename, plus a 300–450 ms server round-trip to

rebuild the panes. For half a second, keystrokes go nowhere — and Windows

beeps at a key sent to a non-editable element.

The fix follows from the diagnosis: in a terminal app, a keystroke with no

focused field belongs to the active pane. It gets delivered there, and if a

switch is in flight — the target pane doesn't exist yet — it's buffered and

replayed into the pane you switched to.

Verified the same way it was found: 0/4 keystrokes delivered before, 4/4 after,

each in the correct session's pane.

Symptom: with several panes open, typing lagged badly.

This one was quick, and it's the clearest example of a class of bug agents are

bad at: nothing is wrong anywhere. Every line of code is correct. The daemon

read PTY output and emitted one IPC message per chunk. Each message crossed

into the WebView and hit a single JavaScript thread. With several panes

producing output, that thread spent its life in message dispatch instead of

rendering.

The fix was to drain what's already available and merge consecutive chunks from

the same pane into one message, capped at 64 KiB.

No bug was fixed. An accounting decision was changed. That's a category agents

rarely reach for on their own, because there's no error to find.

Worth a mention, because it cost me an hour of confusion.

I fanned a task out across several agents, each in its own isolated git

worktree. All of them reported success in careful, structured prose. Not one

had written a single file: in non-interactive mode they needed an explicit

permission flag to touch the filesystem, and without it they narrated the work

they would have done.

What saved me was that the review step didn't read their reports — it ran

git status

in each worktree and counted changed files. It said 0

, and the

0

was true.

Trust the artifact, never the report. This is the single most useful habit

I've taken from working this way.

Agents produced a working multiplexer faster than I could have alone. They also

produced four consecutive confident wrong fixes for the first bug, and would

have produced a fifth if I'd asked.

The thing they don't do on their own is stop and go get evidence. They

generate plausible next steps, and plausible is exactly the failure mode you

have to defend against. Every one of these three bugs was solved by the same

move: capture the state at a layer nobody was looking at.

None of that is exotic. It's ordinary debugging discipline — the kind that's

easy to skip when something is handing you a confident answer every thirty

seconds. If you take one thing from this: when three fixes in a row fail, stop fixing. Your shared assumption is the bug.

wimux is MIT-licensed and runs on Windows 10/11:

cargo install wimux wimux-server

Repo, demo GIF and the full architecture write-up:

github.com/fabperso/wimux

Happy to answer questions — especially about ConPTY, which was by far the

messiest part.

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

Run your AI side-project on zahid.host

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

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/three-bugs-my-ai-age…] indexed:0 read:6min 2026-07-28 ·