Bubble Tea's input reader races the tmux attach reader for stdin
Summary
While a session is attached, two goroutines read os.Stdin concurrently. The
impact is small and bounded, but it silently swallows input and it makes the
terminal handoff on attach non-deterministic.
The two readers
Bubble Tea's reader. app.Run (app/app.go) creates the program and never
releases the terminal for the lifetime of the app. Bubble Tea starts its own
reader goroutine ([email protected]/tty.go:91, go p.readLoop()), which sits in
input.Read(buf[:256]) (key.go:565) on os.Stdin.
The tmux attach reader. TmuxSession.Attach (session/tmux/tmux.go) spawns
a goroutine that also reads os.Stdin and forwards to the session PTY.
They overlap because the attach callback runs synchronously on the event-loop
goroutine and blocks until detach: handleKeyPress → showHelpScreen(...) with
an onDismiss closure (app/app.go), invoked directly at app/help.go:159-161
(or via ui/overlay/textOverlay.go:30-38 on first attach), which then blocks on
<-ch. So Update never returns while a session is attached.
Why the impact is bounded
p.msgs is unbuffered (tea.go:236) and Update is its only receiver
(tea.go:508). So the sequence is:
- Bubble Tea's reader wins one race, reads a chunk of stdin, parses it, and
blocks forever on
msgs <- msg(key.go:595-603). - From that point it is parked and never reads stdin again until detach.
Net effect: roughly one stdin chunk swallowed per attach — an occasional
lost first keystroke right after attaching. Not catastrophic, but it is also
part of why the // Nuke the first bytes of stdin 50 ms heuristic in Attach
exists: some of the query replies it discards are answers to queries the outer
app issued, which a clean handoff would drain deterministically instead of by
timer.
Suggested fix
tea.Exec ([email protected]/exec.go:22-26, 58-65) is Bubble Tea's sanctioned
mechanism — it releases the terminal, runs the child, and restores, all inside
the event loop.
I'd avoid calling ReleaseTerminal/RestoreTerminal directly, for what it's
worth:
ReleaseTerminaltakes stdin out of raw mode (tea.go:779→tty.go:41-60), and nothing else incssets raw mode, so the manual PTY relay would break.- It exits the alt screen, so every attach and detach would flicker.
app.Rundiscards the*tea.Program, so it isn't reachable from the model without plumbing.- Calling it from inside
UpdateriskswaitForReadLoop(tty.go:109-117) stalling 500 ms and thenRestoreTerminalstarting a secondreadLoopwhile the first is still parked on the send — trading one racing reader for two.
Happy to attempt a patch if you have a preference on the approach. Filing this separately from the Ctrl+Q detach fix, which stands on its own.
Source: smtg-ai/claude-squad