This document exists to share some of the challenges of making Pi work well with terminal multiplexers. Some ideas of what a good multiplexer should do. Part of this document was created with the support of a clanker going through GitHub issues.
A few things that should be clarified ahead of time: what makes multiplexers hard for both the multiplexers and the apps are decades of TTY complexities and it’s a fundamentally hard problem today. As a result, Pi finds itself in a constant battle of trying to support capabilities, but then breaking something when a multiplexer is involved. Some of it can be improved in Pi, but it increases the overall complexity greatly and we cannot guarantee that it will not regress something else. This is for instance the case with the different capability layers of the Kitty keyboard protocol.
Thus this document is not a criticism to any multiplexer but just to share why it’s hard and what type of challenges we’re running into.
Pi’s goal is to keep the core functionality testable and to keep complexity out as much as possible. So we’re trying to push back on catering to every situation to keep the overall software manageable.
Proposed Improvements
These are some improvements / wishful behaviors based on issues in Pi. For a more detailed breakdown see the section below.
Keyboard Input
Keyboard handling is the biggest source of trouble. A reliable multiplexer should preserve modifier information for ordinary and special keys, including Enter, Tab, Backspace, arrows, Home, End, PageUp, PageDown, Alt, Ctrl, Shift, and Super. If the application enables an enhanced keyboard protocol, the multiplexer should maintain that state per pane and report only the flags it can actually support.
This matters most for Kitty keyboard protocol. If the multiplexer proxies a Kitty query to the outer terminal, it must not let the outer terminal claim full support unless the multiplexer itself can preserve that full protocol into the pane. Zellij-style mixed behavior, where Shift+Enter arrives as Kitty CSI-u while Alt keys still arrive as legacy escape sequences, is extremely hard for applications to reason about. If mixed behavior is unavoidable, it needs to be explicit and discoverable.
The multiplexer should also avoid treating paste content as keyboard input. Pasted newlines should remain pasted newlines, not be rewritten into Ctrl+J or CSI-u key events. Escape sequences should be delivered in bounded, documented ways so applications do not need fragile timeout guesses to decide whether an isolated Escape byte is a real Escape key or the start of a longer sequence.
Paste
Bracketed paste should be preserved exactly. The multiplexer should keep paste content opaque across all paste paths: terminal paste, multiplexer paste buffer, popup panes, modal panes, remote panes, and SSH sessions. If bracketed paste is unavailable, applications need a truthful way to know that. Timing heuristics are a shitty workaround.
A recurring Pi failure came from tmux paths that rewrote pasted newlines into CSI-u Ctrl+J sequences, that should never be necessary.
Capability Negotiation
A multiplexer should provide truthful answers to terminal capability queries. Environment variables are not sufficient because inside a multiplexer they often describe stale outer-terminal state, the multiplexer itself, or a mixture of both.
The mux should either answer queries itself or proxy them while accounting for what it can actually preserve. Some of this definitely is running into fundamental missing APIs.
Escape-Sequence Passthrough
Raw passthrough is useful but dangerous. A better multiplexer should make passthrough feature-aware and pane-aware. Applications need to know whether OSC 8 hyperlinks, OSC 52 clipboard, Kitty graphics, or other protocols can pass through reliably. The multiplexer should preserve sequence boundaries and avoid silently swallowing sequences.
Silent failure is especially bad for hyperlinks. If an application renders a Markdown link as OSC 8 and the multiplexer drops the target, the visible URL may disappear. In that situation the application would have been better off rendering plain text.
Images and Graphics
Terminal image protocols need more than raw passthrough. Direct image placement often uses coordinates or image IDs that make sense to the outer terminal but not to a pane inside a multiplexer. Today images can duplicate, appear in the wrong place, leak into adjacent panes, disappear after redraws, or corrupt rendering.
An ideal multiplexer would virtualize images per pane. It would track image IDs per pane, clip images to pane bounds, handle pane splits and resizes, preserve images through scrolling and tab switches, and reliably support deletion and replacement. If it cannot provide those semantics, it should advertise images as unsupported rather than letting applications attempt fragile partial rendering.
Hyperlinks
OSC 8 hyperlinks should be implemented or passed through per pane. The multiplexer should preserve the target URL, wrapping behavior, and scrollback behavior where possible. If it cannot do that, it should make the lack of support discoverable so applications can render visible URLs instead.
Automatic fallbacks within the multiplexer would however most likely break rendering in fun ways, so not sure if this can be done without some new APIs.
Color and Theme Detection
A multiplexer should expose color support accurately. It should not inherit COLORTERM=truecolor if it cannot actually pass truecolor correctly or fall back itself. It should support background and foreground color queries, or explicitly report that they are unavailable. It should also avoid misparsing truecolor SGR sequences as unrelated 256-color or style codes, which can result in completely unreadable text.
Theme detection is similarly affected. If the mux blocks background color queries such as OSC 11 and hides the outer terminal identity, applications cannot reliably choose a light or dark theme.
There is an assumption today that this does not magically change after the application starts, but this unfortunately is dynamic when someone re-attaches to a mux later from a different term. Not sure if there is an API today already that one can support to detect dynamic theme changes.
Focus, Mouse, Detach, and Dead Panes
Focus and lifecycle behavior should be pane-local and reliable. If focus events are enabled, the mux should forward focus-in and focus-out for the active pane. If a pane is detached, killed, or loses its host terminal, applications should receive reliable EOF, SIGHUP, or write errors. Writes to a dead pane should fail promptly. They should not leave applications spinning on a deleted tty.
Terminal modes should also survive detach and reattach predictably. If the mux resets bracketed paste, keyboard protocol, mouse mode, or scroll mode on reattach, applications need to know or receive a reset event. Otherwise the application believes a mode is still enabled while the terminal has silently forgotten it.
Scrollback and Redraws
Terminal scrollback position query and manipulation APIs are needed for modern TUIs, particularly for multiplexers. Applications cannot portably know whether the user is scrolled back, cannot restore the user’s scroll position after a full redraw, and cannot reliably keep an input area anchored while using terminal-managed scrollback. For an app like Pi that does not take over the entire viewport, repainting while scrolled results in a terrible user experience.
Debuggability
Terminal bugs are hard because screenshots rarely show the cause. A good multiplexer should make protocol state inspectable. TUI authors need to see raw input bytes sent to the pane, output bytes after mux transformation, active keyboard protocol flags, negotiated terminal modes, passthrough status, and per-pane capability state.
Major Recurring Challenges
Just some flavor of issues we see with multiplexers in pi so far.
Modified Keys in tmux
Shift+Enter,Ctrl+Enter,Alt+Enter,Ctrl+Alt+letter, Backspace, Home/End, and PageUp behaved differently under tmux.- tmux strips modifier information unless configured.
- Different tmux configs emit different protocols.
Currently means that someone needs to support both tmux key formats (xterm modifyOtherKeys, CSI-u via extended-keys-format csi-u). We also added tmux setup docs and startup warnings with a recommended config.
set -g extended-keys on
set -g extended-keys-format csi-u
One of the most significant remaining issues is that when a terminal remaps Shift+Enter to raw \n, Pi cannot know whether that was Shift+Enter or Ctrl+J. Users must bind ctrl+j or remove conflicting terminal/tmux bindings (Thanks to old Claude Code versions we have seen some patched Ghostty configs).
Zellij Mixed Keyboard Mode
These are issues “unique” to Zellij:
- Zellij can expose a mixed keyboard world: some keys look Kitty-like, others arrive as legacy
ESCsequences. - A Zellij-specific workaround fixed Alt bindings for one release but broke Shift+Enter.
To improve this we would need to implement proper Kitty progressive-enhancement negotiation and parse input per sequence, not based on one global “Kitty active” flag but that turns out to be quite messy, so we opted against it for the moment.
Paste Handling
Paste continues to be rather painful:
- Without bracketed paste markers, pasted newlines are indistinguishable from Enter.
- tmux popups / older tmux versions can rewrite pasted newlines as CSI-u Ctrl+J (
ESC[106;5u). - Reattaching tmux sessions can leave bracketed paste / keyboard modes disabled.
We now handle pastes by decoding CSI-u Ctrl+letter sequences inside bracketed paste and we documented/worked around other cases by requiring bracketed paste and newer tmux versions.
Images and Hyperlinks
The failure cases here are rather frustrating and so far we mostly disabled image support in more cases than we want.
- Kitty images inside tmux require passthrough and are still brittle.
- Direct image placement can duplicate, misplace, or leak across panes.
- OSC 8 hyperlinks may be swallowed by tmux/screen, causing URLs to disappear.
Currently we are doing this:
- Images are disabled under tmux/screen by default.
- Hyperlinks are disabled under tmux/screen by default.
U=1Kitty placeholders were investigated but rejected as too fragile to maintain (we looked at it multiple times).
Redraws and Scrollback
Pi does not use an alternate screen so repainting and then affecting scroll positions continues to be an issue for us.
- Full redraws can clear scrollback or jump the viewport.
- Resizes, external editor return, and tool-output toggles can require replaying the screen.
- Terminals do not expose a portable API to query or restore scrollback position.
Terminal Lifecycle Failures
Some of these were just bugs in pi, but they showed up specifically with tmux:
- Closing tmux panes or dropping SSH can leave Pi writing to a dead tty.
SIGHUP/SIGTERMoriginally did not always run extension shutdown.Ctrl+Z/fgcould resume unreliably if Node had no live handles.
There is now explicit SIGHUP (in case term is gone) and SIGTERM shutdown handling and we added a bunch of “dead terminal” cases which result in an emergency exit (EIO, EPIPE etc.).