">
 

Three bugs my AI agents couldn't fix

Iniciado por joomlamz, Hoje at 18:25

Respostas: 0   |   Visualizações: 2

Tópico anterior - Tópico seguinte

0 Membros e 1 Visitante estão a ver este tópico.

Three bugs my AI agents couldn't fix



Tópico: Three bugs my AI agents couldn't fix
Categoria: Tutoriais | Programação & Tecnologia
Idioma Principal: Português (Conteúdo de Tecnologia)

Descrição do Conteúdo / Informações:
-------------------------------------------------------------------------
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.



Bug 1: the corruption that wasn't where everyone looked


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:

• Stop reparenting the DOM — position panes absolutely instead. Still broken.

• Have the daemon push a fresh snapshot after each resize. Still broken.

• Recreate the xterm instance on structural changes. Still broken.

• Debounce the reattach so it happens after the layout settles. Still broken.

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.



Bug 2: the keystroke that vanished


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:


The click→focus mechanics. I rebuilt the exact handler in a bare Chromium
page — same xterm version, same mousedown → term.focus(). The first
keypress went through, every time. Not it.


A layout re-render stealing focus. Reading the code: on an unchanged
layout it's a no-op, and the refocus is guarded. Not it.


Another process stealing the foreground. I logged GetForegroundWindow
and GetGUIThreadInfo while typing. Focus never moved. Not it.


Focus churn on every keystroke. An in-page overlay logging
document.hasFocus() showed all keystrokes landing on the terminal's
textarea. The blur/focus events I'd blamed were me alt-tabbing. Not it.


Terminal focus reporting (DECSET 1004) — a beautiful theory: the terminal
emits 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. Not it.

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.



Bug 3: the lag that was an accounting problem


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.



The agents that reported success and wrote nothing


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.



What I'd tell you about building 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.

• Bug 1: read the grid from the server instead of the browser.

• Bug 2: log the component boundaries to a file, then build a harness that
hammers the timing window a human can't hit.

• Bug 3: notice that nothing was broken, and count messages.

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.


Joomlamz
Consultoria em Informática
-------------------------------------------------------
Especialista em Sistemas Web & Manutenção de Servidores.
A desenvolver o novo AplPortal com suporte a PHP 8.
Precisa de ajuda profissional? Contacte-me.

Tags: