Last month I was debugging a Claude Code agent session that had gone haywire in a tmux split. The agent’s output was bleeding escape codes into the wrong pane — cursor jumping to column 47 when it should’ve been at 0, color state leaking between splits, rendering that made no sense. I’d seen glitches like this for years and never questioned them. Terminal multiplexers just do that sometimes.
Then I read Kovid Goyal’s argument that tmux and screen aren’t buggy — they’re architecturally broken. And it reorganized how I think about my entire terminal workflow.
Goyal is the creator of Kitty, a terminal emulator — the program that actually draws your text, colors, and cursor on screen. His claim: terminal multiplexers (tools like tmux that split one terminal window into multiple sessions, each running its own shell or program) are “completely an anti-pattern” for local development. Not because the code is bad. Because the design guarantees problems that no implementation can fix.

Three architectural problems — not three bugs
1. Every byte hits two parsers
A terminal multiplexer works by running a terminal emulator inside your terminal emulator. When your program writes output, the multiplexer reads every byte, interprets it through its own emulator (updating its internal model of what the screen should look like), and then re-encodes that output for your actual terminal. Two full parsers processing the same stream.
For normal output, you don’t feel it. For high-throughput work — streaming logs, large diffs, build output — it’s measurable. And no optimization in tmux’s codebase can eliminate the second parser. It’s the architecture, not the implementation.
2. Features die at the middleman
For any new terminal feature to reach you, both your terminal and your multiplexer must support it. The multiplexer becomes an involuntary gatekeeper.
Kitty has a graphics protocol that renders images inline in the terminal. If you run tmux inside Kitty, you can’t use it — not because Kitty doesn’t support it, but because tmux doesn’t. Goyal tried to coordinate with the tmux maintainer to add support. It didn’t happen. So the feature simply doesn’t exist for tmux users, regardless of what their terminal can do.
3. Two enormous state machines that have to stay synchronized
A terminal emulator tracks a massive amount of internal state: current text color, bold/italic/underline, cursor position and mode, and hundreds of “escape codes” — the \x1b[... sequences that control everything from cursor movement to background color. This is a state machine: a program whose behavior depends on accumulated internal state that changes with every input sequence.
A multiplexer runs its own state machine while translating between its model and the host terminal’s model. Two slightly different, enormously complex state machines bridged in real time.
The simplest case: a carriage return (\r) normally moves the cursor to column 0 of the current line. In a 120-column terminal with a tmux split, the right pane starts at column 61. Tmux has to intercept the raw \r and emit an absolute cursor-position command that lands at 61, not 0. And carriage return is literally the easiest cursor movement there is.
Search any sophisticated terminal program’s bug tracker for “tmux” and you’ll find a category of bugs that reproduce only inside multiplexers. That’s the architecture leaking through.
The inverted architecture
Goyal’s alternative flips the responsibilities:
- The terminal does window management, splitting, and display — its actual job.
- The multiplexer does exactly one thing: session persistence. Keep your programs alive when the connection drops.
Each window gets its own dedicated TTY — the kernel device that pipes data between programs and terminals. No state bridging. No double parsing. New terminal features work immediately because there’s no middleman to update.
The catch: this only works if your terminal supports the features. It’s not universal like tmux. But if you’ve already committed to a specific terminal, it’s strictly better. WezTerm ships this architecture. Kitty exposes the primitives so you can build your own.
Kitty’s building-block approach
Instead of a monolithic multiplexer, Kitty provides composable primitives:
the mechanism — and a script you can actually run give me the detail
Why the double-interpretation cost is real. Every terminal sits on a pseudo-terminal pair (/dev/pts/*): the master fd your shell writes to, and the slave fd the kernel presents to the running program. When you add tmux, it inserts a second pty pair: tmux owns the master of your app’s pty, interprets every byte (updating its internal VT state machine), then re-encodes the output and writes it into the pty that your actual terminal reads. Two full DFA-style parsers — VT100/VT220/xterm-256color state machines — where one would do. Libraries like libvterm and vte make the scope of this parser visible: each implements hundreds of escape sequences and dozens of parser states just for one side of the bridge.
The carriage-return rewrite, concretely. \r (0x0D) is “move cursor to column 0 of the current line.” Inside a 120-column terminal running a tmux split, the right pane starts at column 61. tmux must intercept the raw \r and emit \x1b[<row>;<col>H (CUP — cursor position absolute) to land the cursor at column 61 instead. Every cursor-movement code has a similar story.
Try it yourself — scriptable session switching without tmux. Kitty exposes its state over a Unix socket via kitten @. This script opens a named “project” tab or creates one if it doesn’t exist, without a multiplexer in the middle:
#!/usr/bin/env bash
# Usage: switch-project.sh <project-name>
PROJECT="${1:?need a project name}"
# Find a tab whose title matches; kitten @ ls returns JSON
MATCH=$(kitten @ ls --self 2>/dev/null \
| jq -r ".[] | .tabs[] | select(.title == \"$PROJECT\") | .id")
if [[ -n "$MATCH" ]]; then
kitten @ focus-tab --match "id:$MATCH"
else
kitten @ new-tab --tab-title "$PROJECT"
kitten @ send-text --match "title:$PROJECT" "cd ~/$PROJECT && clear\r"
fiNo second pty. No state-machine bridge. New terminal features (Kitty’s graphics protocol, Unicode text sizing, hyperlinks) work immediately in every tab.
The philosophy: give users building blocks and let them compose their own workflows, instead of shipping a complete but rigid system. It’s more up-front work than tmux’s “install and go,” but the result is exactly what you need — no more, no less.
The one thing tmux still wins at
Goyal is direct: traditional multiplexers are still the right answer for remote persistence over flaky connections. When you SSH into a server and your connection might drop, you need the running process to survive disconnection. That requires a server-side process that owns the TTY independent of your SSH session — exactly what tmux and screen provide.
For local development? “Completely an anti-pattern.” Everything you do locally with tmux can be done natively.
And Kitty’s remote control isn’t limited to the local machine — it works cross-process on the same box, cross-machine over TCP, and is fully scriptable. That opens interesting workflows across multiple machines without the multiplexer overhead.
What I actually changed
I didn’t throw tmux away. I narrowed it:
- Local dev: Terminal-native splits and tabs. Kitty handles layout. Shell scripts handle session switching.
- Remote servers: tmux stays. Session persistence over SSH is its one irreplaceable job.
- Custom scripts: Built lightweight shell wrappers for the session-switching behavior I actually used — not the 80% of tmux features I never touched.
The result: fewer “why does this only break in tmux?” bugs, faster terminal output, and a workflow that matches what I actually do instead of conforming to tmux’s opinions about how I should work.
The pattern underneath
This is a software pattern that shows up everywhere: a solution that was elegant in one era becomes legacy drag in the next, and nobody notices because everyone assumes infrastructure this widely used must be correctly architected. Tmux solved real problems when terminals were dumb display devices that couldn’t manage multiple sessions. Modern terminals can do all of that natively — but we kept running the middleman out of habit.
The question isn’t “is tmux bad?” It’s “does inserting a second terminal emulator between my program and my screen still make sense given what terminals can do today?” For remote persistence, yes. For everything else, I was paying an architectural tax for features my terminal already provided — and I didn’t notice until I read someone willing to say it out loud.