Summary
Since v1.4.1, launching the agent-deck TUI leaves the terminal in a state where input is broken: arrow keys are not interpreted as navigation, mouse-wheel scroll triggers iTerm2's "mouse reporting was left on" warning, and the TUI is essentially unusable. Affects every terminal tested, including Ghostty — the exact terminal the v1.4.1 change (#535) was supposed to fix.
Versions
| Version |
Status |
| v1.4.0 and earlier |
input works |
| v1.4.1 |
regression introduced (commit 2afee24, "fix(#535): wire CSIuReader into tea.NewProgram input pipeline") |
Symptoms
agent-deck launches; sidebar, sessions, and status bar all render correctly.
- Pressing arrow keys to move between sessions does nothing useful — instead,
^[[A / ^[[B characters appear in the visible TUI output.
- Mouse-wheel scroll does not navigate or scroll. iTerm2 raises:
"looks like mouse reporting was left on when ssh session ended unexpectedly or an app misbehaved"
- The TUI is unusable — there's no way to switch sessions or scroll content.
- Quitting the TUI also leaves the parent shell in a bad state (cooked mode + mouse reporting on);
reset is needed to recover.
Reproduction
- Install v1.4.1 via brew or
agent-deck self-update
- From a fresh terminal tab (iTerm2 or Ghostty), run
agent-deck
- Try to navigate with arrow keys → see
^[[A/^[[B
- Try to scroll with mouse wheel → see iTerm2 warning, no scroll
Tested on macOS 15 + iTerm2 and macOS 15 + Ghostty. Both broken in identical ways.
Notable: Ghostty was the terminal #535 was filed against. The v1.4.1 fix doesn't even fix the case it was filed for — it just breaks every terminal equally.
Root cause
Commit 2afee24 added one line to cmd/agent-deck/main.go:
tea.WithInput(ui.NewCSIuReader(os.Stdin)),
Two compounding bugs:
1. Bubble Tea silently skips raw-mode setup
Bubble Tea only puts the TTY into raw mode when its input is the real *os.File for stdin. Wrapping stdin in a *csiuReader makes the type assertion fail; Bubble Tea skips raw-mode setup. The TTY stays in cooked mode (echo on, line-buffered), so:
- Arrow keys generate
ESC [ A/ESC [ B. The kernel tty driver echoes them as ^[[A/^[[B. Bubble Tea never sees them as KeyMsg events.
tea.WithMouseCellMotion() still emits \033[?1000h to enable mouse reporting at startup, but cooked mode prevents Bubble Tea from consuming the events. iTerm2 detects the orphaned mouse reporting and shows its leak warning.
2. csiuReader.translate does not recognise SGR mouse terminators
internal/ui/keyboard_compat.go's scan loop uses a hardcoded whitelist of CSI final bytes: u, A, B, C, D, H, F, ~. It is missing M and m, the terminators for SGR mouse events (ESC [ < button ; col ; row M/m). When a mouse event arrives, the scanner walks past it looking for a known terminator, eventually bundles the mouse bytes with whatever later sequence terminates the scan, and passes the whole blob through as one "unknown CSI". Mouse handling is broken even with raw mode forced back on.
The full set of CSI final bytes is @–~ (0x40–0x7E). A whitelist of 8 of them is fundamentally not a CSI parser; many other inputs (focus events, status reports, etc.) will also be corrupted.
Workaround
Downgrade to v1.3 (last fully clean release) or v1.4.0 (still has the #531 / #533 regressions but input works):
curl -L https://github.com/asheshgoplani/agent-deck/releases/download/v1.3/agent-deck_1.3_darwin_arm64.tar.gz \
| tar xz -C /tmp && mv /tmp/agent-deck ~/.local/bin/agent-deck
Note on macOS Sequoia: when copying the binary into place over a running agent-deck process, use cp foo agent-deck.new && mv -f agent-deck.new agent-deck (atomic rename) — direct cp over the file may fail because the running TUI holds the inode.
Proposed fix
Revert commit 2afee24. The pre-existing DisableKittyKeyboard(os.Stdout) call at cmd/agent-deck/main.go:607 already asks every Kitty-protocol-aware terminal (Ghostty, Foot, Alacritty, Kitty) to fall back to legacy reporting, which is what #535 was trying to achieve. The reader was unnecessary belt-and-suspenders that turned out to be a foot-gun.
PR submitted as #543.
Why a "proper fix" is more involved than the original
If the wrapper is to come back, it needs:
- Explicit
term.MakeRaw(int(os.Stdin.Fd())) + defer term.Restore(...) around tea.NewProgram so raw mode is set regardless of input wrapping.
- A real CSI parser in
csiuReader.translate instead of an 8-byte terminator whitelist (params + intermediates + final byte across @–~).
- Manual verification on Ghostty / Foot / Alacritty that the wrapper actually helps a real case the existing
DisableKittyKeyboard call doesn't already cover.
Until that work happens, reverting is the right move.
Summary
Since v1.4.1, launching the agent-deck TUI leaves the terminal in a state where input is broken: arrow keys are not interpreted as navigation, mouse-wheel scroll triggers iTerm2's "mouse reporting was left on" warning, and the TUI is essentially unusable. Affects every terminal tested, including Ghostty — the exact terminal the v1.4.1 change (#535) was supposed to fix.
Versions
Symptoms
agent-decklaunches; sidebar, sessions, and status bar all render correctly.^[[A/^[[Bcharacters appear in the visible TUI output.resetis needed to recover.Reproduction
agent-deck self-updateagent-deck^[[A/^[[BTested on macOS 15 + iTerm2 and macOS 15 + Ghostty. Both broken in identical ways.
Notable: Ghostty was the terminal #535 was filed against. The v1.4.1 fix doesn't even fix the case it was filed for — it just breaks every terminal equally.
Root cause
Commit 2afee24 added one line to
cmd/agent-deck/main.go:Two compounding bugs:
1. Bubble Tea silently skips raw-mode setup
Bubble Tea only puts the TTY into raw mode when its input is the real
*os.Filefor stdin. Wrapping stdin in a*csiuReadermakes the type assertion fail; Bubble Tea skips raw-mode setup. The TTY stays in cooked mode (echo on, line-buffered), so:ESC [ A/ESC [ B. The kernel tty driver echoes them as^[[A/^[[B. Bubble Tea never sees them asKeyMsgevents.tea.WithMouseCellMotion()still emits\033[?1000hto enable mouse reporting at startup, but cooked mode prevents Bubble Tea from consuming the events. iTerm2 detects the orphaned mouse reporting and shows its leak warning.2.
csiuReader.translatedoes not recognise SGR mouse terminatorsinternal/ui/keyboard_compat.go's scan loop uses a hardcoded whitelist of CSI final bytes:u, A, B, C, D, H, F, ~. It is missingMandm, the terminators for SGR mouse events (ESC [ < button ; col ; row M/m). When a mouse event arrives, the scanner walks past it looking for a known terminator, eventually bundles the mouse bytes with whatever later sequence terminates the scan, and passes the whole blob through as one "unknown CSI". Mouse handling is broken even with raw mode forced back on.The full set of CSI final bytes is
@–~(0x40–0x7E). A whitelist of 8 of them is fundamentally not a CSI parser; many other inputs (focus events, status reports, etc.) will also be corrupted.Workaround
Downgrade to v1.3 (last fully clean release) or v1.4.0 (still has the #531 / #533 regressions but input works):
Note on macOS Sequoia: when copying the binary into place over a running agent-deck process, use
cp foo agent-deck.new && mv -f agent-deck.new agent-deck(atomic rename) — directcpover the file may fail because the running TUI holds the inode.Proposed fix
Revert commit 2afee24. The pre-existing
DisableKittyKeyboard(os.Stdout)call atcmd/agent-deck/main.go:607already asks every Kitty-protocol-aware terminal (Ghostty, Foot, Alacritty, Kitty) to fall back to legacy reporting, which is what #535 was trying to achieve. The reader was unnecessary belt-and-suspenders that turned out to be a foot-gun.PR submitted as #543.
Why a "proper fix" is more involved than the original
If the wrapper is to come back, it needs:
term.MakeRaw(int(os.Stdin.Fd()))+defer term.Restore(...)aroundtea.NewProgramso raw mode is set regardless of input wrapping.csiuReader.translateinstead of an 8-byte terminator whitelist (params + intermediates + final byte across@–~).DisableKittyKeyboardcall doesn't already cover.Until that work happens, reverting is the right move.