VS Code integrated terminal over SSH leaks kitty keyboard protocol sequences after exiting Codex (e.g. 9;1:3u)
Open 💬 7 comments Opened May 22, 2026 by Wunrry
💡 Likely answer: A maintainer (github-actions[bot], contributor)
responded on this thread — see the highlighted reply below.
What version of Codex is running?
codex-cli 0.133.0
What platform is your computer?
Local machine:
- VS Code integrated terminal over Remote SSH
- VS Code terminal setting:
terminal.integrated.enableKittyKeyboardProtocol = true TERM_PROGRAM_VERSION=1.121.0
Remote machine:
- Debian GNU/Linux 13 (trixie)
- Linux 6.12.88+deb13-amd64 x86_64
- Shell: bash
TERM=xterm-256colorTERM_PROGRAM=vscode
What steps can reproduce the bug?
- Open a fresh VS Code integrated terminal connected to a Linux host via SSH.
- Start
codex. - Exit
codex. - Press
Tabin the shell.
This reproduces consistently in a fresh SSH session.
What is the expected behavior?
After exiting Codex, the shell should return to normal terminal input behavior and pressing keys like Tab should not print raw escape-sequence fragments.
What do you see instead?
After exiting Codex, the shell prints raw fragments such as:
9;1:3u
Sometimes I also see:
9;5:3u
These appear to correspond to kitty keyboard protocol key release events leaking into the shell instead of being fully cleaned up on exit.
Additional information
- This does not appear to be a VPS locale/encoding problem.
- The issue disappears if I disable the local VS Code setting:
"terminal.integrated.enableKittyKeyboardProtocol": false
- I tested this in a fresh SSH session and can reproduce it reliably by entering and exiting Codex.
- I also tested a Bash-side workaround that tried to reset keyboard enhancement flags on each prompt, but it did not reliably fix the problem in VS Code's integrated terminal.
- This suggests an interoperability issue between Codex terminal cleanup on exit and VS Code/xterm.js kitty keyboard protocol handling.
Minimal environment evidence
codex-cli 0.133.0
Debian GNU/Linux 13 (trixie)
Linux 6.12.88+deb13-amd64 x86_64 GNU/Linux
TERM=xterm-256color
TERM_PROGRAM=vscode
TERM_PROGRAM_VERSION=1.121.0
7 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
I am seeing the same class of leaked keyboard-protocol bytes, but outside VS Code/SSH.
Environment
codex-cli 0.141.0Darwin 25.5.0 arm64 armTERM=xterm-ghosttyTERM_PROGRAM=ghosttygpt-5.5Symptom
After pressing
Ctrl+Cto terminate/exit Codex in the TUI, control returns to the parent shell and the shell sometimes displays:This appears after Codex has already exited, so it looks like pending enhanced keyboard / CSI-u bytes are reaching the shell.
Reproduction
codex.Ctrl+Cto terminate/exit the TUI.9;5:3umay appear at the shell prompt after Codex exits.Additional note
This makes the issue look broader than VS Code's integrated terminal. The
:3usuffix is consistent with key release events in enhanced keyboard reporting. A plausible mechanism is that Codex exits on theCtrl+Cpress event while the terminal later emits the corresponding release event, and that release event arrives after Codex has restored terminal modes and stopped reading input.I can reproduce what looks like the same issue in native kitty, not VS Code or another terminal wrapper.
Environment:
This happens when exiting Codex with
Ctrl-C.In my shell it presents as:
<img width="732" height="306" alt="Image" src="https://github.com/user-attachments/assets/8c79247a-c0d6-42be-a116-9cca7f18ff7c" />
The
execute:part is likely zsh vi mode interpreting:asexecute-named-cmd, so this may be the same leaked:3u/ CSI-u key-release fragment reported here, just rendered differently by zsh vi mode.I can reproduce the same leaked keyboard-protocol sequence outside VS Code/SSH.
Environment:
codex-cli 0.142.5Symptom after Codex exits:
Ctrl+Dleaves/prints0;1:3uCtrl+Cleaves/prints9;5:3uWorkaround confirmed: launching with
CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENT=1 codexfixes it for me.I can reproduce this on native iTerm2, without SSH or a terminal multiplexer.
Environment
0.144.126.5.2(25F84), arm643.6.115.9TERM=xterm-256colorTERM_PROGRAM=iTerm.appTERM_PROGRAM_VERSION=3.6.11COLORTERM=truecolorLANG=en_CA.UTF-8LC_ALL=C.UTF-8LC_CTYPE=C.UTF-8CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENTis unsetObserved behavior
At the idle Codex prompt:
/quitand/exitexit cleanly.Ctrl+Csometimes exits and leaves9;5:3uat the zsh prompt.Ctrl+Dsometimes exits and leaves00;5u00;5:3uat the zsh prompt.These appear consistent with visible tails of enhanced keyboard/CSI-u events:
ESC[99;5:3uESC[100;5u/ESC[100;5:3uReproducibility
The behavior is intermittent. Repeating the same exit sequence sometimes leaks the CSI-u fragment and sometimes exits cleanly. Quick taps versus holding Ctrl+C/Ctrl+D longer does not appear to affect the occurrence rate.
/quitand/exithave consistently exited cleanly in my testing.This looks consistent with Codex exiting on the key-press event while the corresponding release event occasionally reaches the parent shell after TUI teardown.
Follow-up with source-level A/B results and a locally tested candidate fix.
Environment remains macOS arm64 + iTerm2 + zsh. This also appears to be the same failure reported for Kitty in #31865.
What the A/B experiments showed
Baseline
codex-cli 0.144.1intermittently leaked CSI-u tails on control-key exit:9;5:3u00;5u00;5:3u/exitand/quitremained cleanI built current main at
9e552e9d15ba52bed7077d5357f3e18e330f8f38with onlyKeyboardEnhancementFlags::REPORT_EVENT_TYPESremoved. That did not fix the issue; it changed the leaked packets to:9;5u00;5uThat result suggests the existing exit reset and suppressing release events are both insufficient: a CSI-u packet can remain unread when the app exit path wins the event-loop race.
Source trace / candidate approach
App::runcan leave itstokio::select!loop on an app exit event before another terminal event is consumed. TheTuievent broker can still own a running crosstermEventStream, whilerestore_after_exit()resets future keyboard reporting but does not discard unread terminal input.The locally tested teardown change is:
The minimal diff is eight added lines in two files:
This reuses the existing
pause_events()and cross-platformflush_terminal_input_buffer()mechanisms. Temporary restore paths such as external-editor handoff remain unchanged.Validation
REPORT_EVENT_TYPESrestored: clean Ctrl+C/Ctrl+D spot checks and normal post-exit shell input so far.just fmt: passed.just test -p codex-tui: 2,988 passed, 4 skipped.tcflushcan theoretically discard input deliberately typed before Codex has returned control to the shell, so that is the main tradeoff to evaluate.The repository's contribution policy asks for analysis/fix outlines in the issue before an external PR. I am happy to prepare the focused patch if a maintainer would like to invite one.
I investigated this further and have a more targeted candidate patch available:
The source trace agrees that this is an event-loop race: Codex can act on the Ctrl+C/Ctrl+D press and begin teardown before the matching CSI-u release packet is consumed. Resetting keyboard flags only affects future reporting, and a teardown-time stdin flush is timing-dependent: it can run before the release arrives and can also discard legitimate shell type-ahead. In a targeted A/B reproduction of the reported burst, the pause/flush approach still leaked in 4/5 runs.
The candidate patch instead does the following on Unix when enhanced keyboard reporting is active:
u, leaving later shell input queued.Validation on current main:
just fmt: passedjust test -p codex-tui: 3,047 passed, 4 skippedTYPEAHEADin the same PTY write: 10/10 preserved exactlyKnown boundary: with a synthetic zero-delay release, crossterm can sometimes pre-read the release before the exclusive handoff. All 30/30 runs still exited, but 14/30 used the timeout fallback. When release and type-ahead were sent in the same zero-delay write, type-ahead was preserved in 6/10 runs and had already been consumed by crossterm in 4/10. This seems inherent to handing off from an already-running buffered reader; the normal delayed-release cases were reliable.
I have not reproduced the exact VS Code Remote SSH environment yet; the PTY matrix covered normal, delayed, repeated, held-key, timeout, enhancement-disabled, and type-ahead cases.
The repository currently limits new PRs to collaborators, so I cannot open this against upstream from the fork. I am happy to open the focused PR if invited, or a maintainer can use/cherry-pick the branch above.