{"slug": "three-bugs-my-ai-agents-couldn-t-fix", "title": "Three bugs my AI agents couldn't fix", "summary": "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.", "body_md": "I spent the last few weeks building [wimux](https://github.com/fabperso/wimux),\n\na terminal multiplexer for Windows written in Rust. `tmux`\n\nand `zellij`\n\nare\n\nUnix-first — on Windows they only really live inside WSL, detached from the\n\nnative shell. I wanted persistent PowerShell sessions, so I wrote them.\n\nMost of the code was written by AI agents. My commits say so explicitly\n\n(`Co-Authored-By:`\n\ntrailers), and I'd rather lead with that than have someone\n\ndiscover it. What I want to write about isn't the code the agents produced —\n\nit's the three bugs they **couldn't** fix, because those turned out to be the\n\ninteresting part.\n\nA quick sketch of the thing, so the bugs make sense: a detached per-user daemon\n\nowns the sessions, drives child processes through **ConPTY** (`portable-pty`\n\n),\n\nand emulates the terminals server-side. Clients — a TUI, a Tauri v2 GUI running\n\nxterm.js, and a scriptable CLI — attach over a Windows named pipe with a\n\n`postcard`\n\nbinary protocol. So: a Rust daemon, a WebView, and a PTY, three\n\nlayers that each have their own idea of what the screen looks like.\n\n**Symptom:** split a pane while a full-screen TUI app was running, and the\n\ndisplay filled with orphaned characters — box-drawing glyphs stranded from a\n\nnarrower render.\n\nEverything pointed at the client. xterm.js is the thing drawing pixels; the\n\nGUI reparents DOM nodes when the layout changes; re-attaching to the session\n\nmade the corruption disappear. Obvious client bug.\n\nWe tried four fixes, in this order:\n\nFour plausible fixes, four failures. Each one was a reasonable theory, and each\n\none was aimed at the client.\n\nThe move that cracked it took thirty seconds: I captured the terminal grid\n\n**from the server**, using the daemon's own view of the pane — the one that has\n\nnever heard of xterm.js.\n\n```\nwimux agent capture -t 0 -p 2\n```\n\nThe orphaned glyphs were there too.\n\nThat single output eliminated the entire client. The corruption existed before\n\na single pixel was drawn. The real cause was upstream of everything we'd been\n\ntouching: splitting a pane sends a **burst of intermediate sizes** to the PTY.\n\nThe app inside redraws at each one, its incremental redraws don't clear what\n\nthey no longer cover, and the leftovers stay in the grid — in the server's grid.\n\nThe fix was three lines: debounce resize events so the PTY only ever sees the\n\nfinal, settled size.\n\n**The lesson isn't \"debounce your resizes.\"** It's that four consecutive fixes\n\nfailing is data. It means the assumption underneath all four is wrong — and no\n\namount of new fixes at the same layer will help. Go get an observation from a\n\n*different* layer instead.\n\n**Symptom:** click a pane, type — the first keypress doesn't appear, and the\n\nmachine beeps. Press again and it works.\n\nThis one resisted for days, partly because every reasonable theory was wrong.\n\nHere's what I eliminated, and how:\n\n`mousedown`\n\n→ `term.focus()`\n\n. The first\nkeypress went through, every time. `GetForegroundWindow`\n\nand `GetGUIThreadInfo`\n\nwhile typing. Focus never moved. `document.hasFocus()`\n\nshowed all keystrokes landing on the terminal's\ntextarea. The blur/focus events I'd blamed were me alt-tabbing. `ESC [ I`\n\non focus, PSReadLine chokes on it, beeps, and eats the next\ncharacter. It explains every symptom. I enabled the mode and watched what\nxterm actually emitted: nothing. ConPTY had swallowed the sequence. Five theories, five refutations, all backed by an observation rather than an\n\nargument. What finally worked was building a **reproduction harness**: a script\n\nthat clicks a pane and sends a keystroke after a delay, sweeping the delay from\n\n0 ms to 150 ms, dozens of times. A human can't hit a 50 ms window on purpose. A\n\nscript does it forty times in a row.\n\nResult: **zero losses.** Clicking *inside* a pane was never the problem.\n\nSo I pointed the harness at the sidebar instead — clicking a session entry to\n\nswitch workspaces. **Four out of four keystrokes lost**, with a log that made\n\nthe mechanism unmistakable:\n\n```\nMD  pane=-  target=SPAN.name  active=TEXTAREA.xterm-helper-textarea\n== BLUR ==                          the terminal loses focus\nKEY \"k\"  active=BODY  target=BODY   the keystroke lands on <body>\n== FOCUS ==                         focus returns ~310 ms later\n```\n\nThe sidebar entries are plain `<div>`\n\ns. They aren't focusable, so clicking one\n\ndrops document focus to `<body>`\n\n. Then two delays stack: a 200 ms timer that\n\ndisambiguates double-click-to-rename, plus a 300–450 ms server round-trip to\n\nrebuild the panes. For **half a second**, keystrokes go nowhere — and Windows\n\nbeeps at a key sent to a non-editable element.\n\nThe fix follows from the diagnosis: in a terminal app, a keystroke with no\n\nfocused field belongs to the active pane. It gets delivered there, and if a\n\nswitch is in flight — the target pane doesn't exist yet — it's buffered and\n\nreplayed into the pane you switched to.\n\nVerified the same way it was found: 0/4 keystrokes delivered before, 4/4 after,\n\neach in the correct session's pane.\n\n**Symptom:** with several panes open, typing lagged badly.\n\nThis one was quick, and it's the clearest example of a class of bug agents are\n\nbad at: nothing is *wrong* anywhere. Every line of code is correct. The daemon\n\nread PTY output and emitted one IPC message per chunk. Each message crossed\n\ninto the WebView and hit a single JavaScript thread. With several panes\n\nproducing output, that thread spent its life in message dispatch instead of\n\nrendering.\n\nThe fix was to drain what's already available and merge consecutive chunks from\n\nthe same pane into one message, capped at 64 KiB.\n\nNo bug was fixed. An accounting decision was changed. That's a category agents\n\nrarely reach for on their own, because there's no error to find.\n\nWorth a mention, because it cost me an hour of confusion.\n\nI fanned a task out across several agents, each in its own isolated git\n\nworktree. All of them reported success in careful, structured prose. Not one\n\nhad written a single file: in non-interactive mode they needed an explicit\n\npermission flag to touch the filesystem, and without it they narrated the work\n\nthey would have done.\n\nWhat saved me was that the review step didn't read their reports — it ran\n\n`git status`\n\nin each worktree and counted changed files. It said `0`\n\n, and the\n\n`0`\n\nwas true.\n\n**Trust the artifact, never the report.** This is the single most useful habit\n\nI've taken from working this way.\n\nAgents produced a working multiplexer faster than I could have alone. They also\n\nproduced four consecutive confident wrong fixes for the first bug, and would\n\nhave produced a fifth if I'd asked.\n\nThe thing they don't do on their own is **stop and go get evidence**. They\n\ngenerate plausible next steps, and plausible is exactly the failure mode you\n\nhave to defend against. Every one of these three bugs was solved by the same\n\nmove: capture the state at a layer nobody was looking at.\n\nNone of that is exotic. It's ordinary debugging discipline — the kind that's\n\neasy to skip when something is handing you a confident answer every thirty\n\nseconds. If you take one thing from this: **when three fixes in a row fail,\nstop fixing. Your shared assumption is the bug.**\n\nwimux is MIT-licensed and runs on Windows 10/11:\n\n```\ncargo install wimux wimux-server\n```\n\nRepo, demo GIF and the full architecture write-up:\n\n[github.com/fabperso/wimux](https://github.com/fabperso/wimux)\n\nHappy to answer questions — especially about ConPTY, which was by far the\n\nmessiest part.", "url": "https://wpnews.pro/news/three-bugs-my-ai-agents-couldn-t-fix", "canonical_source": "https://dev.to/fabperso/three-bugs-my-ai-agents-couldnt-fix-13bn", "published_at": "2026-07-28 16:19:08+00:00", "updated_at": "2026-07-28 16:36:17.174564+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["wimux", "ConPTY", "xterm.js", "Tauri", "Rust", "PowerShell", "PSReadLine"], "alternates": {"html": "https://wpnews.pro/news/three-bugs-my-ai-agents-couldn-t-fix", "markdown": "https://wpnews.pro/news/three-bugs-my-ai-agents-couldn-t-fix.md", "text": "https://wpnews.pro/news/three-bugs-my-ai-agents-couldn-t-fix.txt", "jsonld": "https://wpnews.pro/news/three-bugs-my-ai-agents-couldn-t-fix.jsonld"}}