What happened?
The interactive TUI renders completely — banner, tips, the folder-trust dialog, the auth-selection dialog, and eventually the input prompt — but no keyboard input is ever processed. Digits, arrows, Enter, plain characters: nothing changes the screen, ever. The process stays alive and idle.
This is not a rendering hang and not a TTY problem. The process never subscribes to terminal input at all.
Evidence
With the TUI up and waiting, on the main CLI process:
-
The tty fd is absent from every epoll set. Dumping /proc/<pid>/fdinfo/<epoll fd> for all three epoll instances shows no tfd entry for fd 0 or for the separate /dev/pts/N descriptor the CLI opens. The main thread sits in epoll_pwait(..., -1, ...) forever on a set that contains only pipes and eventfds.
-
No bytes are ever read. rchar in /proc/<pid>/io does not increase by a single byte across repeated keypresses, while the terminal itself is in raw mode (stty -a on the pts reports -icanon -echo -isig, so the CLI did call setRawMode(true)).
-
strace confirms the capability query completes, then nothing follows. Launching under strace -ff -yy -e trace=read,ioctl,epoll_ctl,epoll_wait:
ioctl(23</dev/pts/N>, TCSETSW, {c_iflag=IUTF8, ... c_lflag=ECHOE|ECHOK|ECHOCTL|ECHOKE, ...}) = 0
read(23</dev/pts/N>, "\33P>|tmux 3.4\33\\\33[?1;2;4c", 65536) = 23
The terminal replies to \e[>q (name) and \e[c (device attributes), but never to \e[?u (kitty keyboard) — neither tmux nor VTE support it. After this read there is not a single epoll_ctl(EPOLL_CTL_ADD) for the tty fd in any traced process, and the typed bytes never appear in any trace.
- It is a race, not a deterministic failure. In one run out of roughly twenty, the tty descriptor was present in an epoll set and input worked normally — the typed character appeared in the prompt immediately. Same binary, same terminal, same flags.
Suspected cause
TerminalCapabilityManager.detectCapabilities() attaches a data listener to process.stdin (flowing mode) and waits up to its 1000 ms timeout. Because the terminal never answers the kitty query, that full timeout is always consumed on terminals like VTE and tmux.
Meanwhile Ink subscribes via readable, and KeypressContext only calls setRawMode(true) when stdin.isRaw === false — while raw mode is already enabled by the time that effect runs. Ink's setRawMode is what attaches the readable listener (guarded by rawModeEnabledCount === 0), so the listener is never attached and the fd never enters the event loop.
Mixing flowing-mode (on('data')) capability detection with Ink's readable-based reader on the same stream looks like the underlying race.
Note: patching the wasRaw === false guard to call setRawMode(true) unconditionally, in every bundled copy of the chunk, did not fix it — so the guard alone is not the whole story.
Steps to reproduce
Reproduction is timing-dependent; on the affected machine it fails on essentially every launch.
- On Linux, run
gemini in GNOME Terminal (VTE) or inside tmux.
- Wait for the folder-trust dialog, or use
--skip-trust plus GEMINI_API_KEY to reach the input prompt directly.
- Press digits, arrows, Enter, or type any text.
- Nothing is registered. Confirm with
grep rchar /proc/<pid>/io before and after — the value does not move.
What did you expect to happen?
Keyboard input to be processed by the TUI.
Environment
- OS: Linux (kernel 7.0), Wayland
- Terminals: GNOME Terminal (VTE) and tmux 3.4 — both affected
- Node.js: 24.19.0 and 22.23.2 — both affected
- Gemini CLI: 0.50.0, 0.53.1, 0.54.4, 0.55.1 and 0.56.0-nightly — all affected
- Installed globally via npm (user-level prefix, no sudo)
Ruled out
- Not a TTY issue: a plain Node script in the same terminal does
process.stdin.setRawMode(true) and receives a, �[B, \r correctly.
- Not configuration: reproduced with a completely empty
HOME, no settings file, no extensions.
- Not the relaunch wrapper:
GEMINI_CLI_NO_RELAUNCH=1 behaves identically, with a single process holding the tty.
- Not
ui.renderProcess: setting it to false changes nothing.
- Not the secret service, network reachability,
io_uring (UV_USE_IO_URING=0 makes no difference), or terminal resize (SIGWINCH does not wake it).
- Non-interactive mode (
gemini -p "...") works perfectly.
Possibly related: #23297 (Enter does nothing), #23480 (a different mechanism — a background process stealing stdin — but the same user-visible symptom).
What happened?
The interactive TUI renders completely — banner, tips, the folder-trust dialog, the auth-selection dialog, and eventually the input prompt — but no keyboard input is ever processed. Digits, arrows, Enter, plain characters: nothing changes the screen, ever. The process stays alive and idle.
This is not a rendering hang and not a TTY problem. The process never subscribes to terminal input at all.
Evidence
With the TUI up and waiting, on the main CLI process:
The tty fd is absent from every epoll set. Dumping
/proc/<pid>/fdinfo/<epoll fd>for all three epoll instances shows notfdentry for fd 0 or for the separate/dev/pts/Ndescriptor the CLI opens. The main thread sits inepoll_pwait(..., -1, ...)forever on a set that contains only pipes and eventfds.No bytes are ever read.
rcharin/proc/<pid>/iodoes not increase by a single byte across repeated keypresses, while the terminal itself is in raw mode (stty -aon the pts reports-icanon -echo -isig, so the CLI did callsetRawMode(true)).strace confirms the capability query completes, then nothing follows. Launching under
strace -ff -yy -e trace=read,ioctl,epoll_ctl,epoll_wait:The terminal replies to
\e[>q(name) and\e[c(device attributes), but never to\e[?u(kitty keyboard) — neither tmux nor VTE support it. After this read there is not a singleepoll_ctl(EPOLL_CTL_ADD)for the tty fd in any traced process, and the typed bytes never appear in any trace.Suspected cause
TerminalCapabilityManager.detectCapabilities()attaches adatalistener toprocess.stdin(flowing mode) and waits up to its 1000 ms timeout. Because the terminal never answers the kitty query, that full timeout is always consumed on terminals like VTE and tmux.Meanwhile Ink subscribes via
readable, andKeypressContextonly callssetRawMode(true)whenstdin.isRaw === false— while raw mode is already enabled by the time that effect runs. Ink'ssetRawModeis what attaches thereadablelistener (guarded byrawModeEnabledCount === 0), so the listener is never attached and the fd never enters the event loop.Mixing flowing-mode (
on('data')) capability detection with Ink'sreadable-based reader on the same stream looks like the underlying race.Note: patching the
wasRaw === falseguard to callsetRawMode(true)unconditionally, in every bundled copy of the chunk, did not fix it — so the guard alone is not the whole story.Steps to reproduce
Reproduction is timing-dependent; on the affected machine it fails on essentially every launch.
geminiin GNOME Terminal (VTE) or inside tmux.--skip-trustplusGEMINI_API_KEYto reach the input prompt directly.grep rchar /proc/<pid>/iobefore and after — the value does not move.What did you expect to happen?
Keyboard input to be processed by the TUI.
Environment
Ruled out
process.stdin.setRawMode(true)and receivesa,�[B,\rcorrectly.HOME, no settings file, no extensions.GEMINI_CLI_NO_RELAUNCH=1behaves identically, with a single process holding the tty.ui.renderProcess: setting it tofalsechanges nothing.io_uring(UV_USE_IO_URING=0makes no difference), or terminal resize (SIGWINCH does not wake it).gemini -p "...") works perfectly.Possibly related: #23297 (Enter does nothing), #23480 (a different mechanism — a background process stealing stdin — but the same user-visible symptom).