pingdotgg / pingdotgg/t3code

After a server restart, the new shell inherits terminal modes a dead TUI left on, and key releases or mouse reports leak into zsh

Open
#9,219 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

Before submitting
  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.
Area

apps/server

Steps to reproduce
  1. Open a thread terminal and run codex (or claude). Wait for its prompt.
  2. Quit and relaunch T3 Code, or restart the dev server, while it is still running.
  3. Reopen the same thread's terminal.
  4. Type anything, or scroll with the mouse wheel.
Expected behavior

Plain shell input. Keys arrive as text; scrolling scrolls.

Actual behavior

After Codex CLI, every key release reaches zsh as a Kitty keyboard event:

user@host project % sda^[[100;1:3u^[[115;1:3u^[[97;1:3u
zsh: command not found: 1:3u

After Claude Code, wheel and motion reach zsh as SGR mouse reports:

user@host project % 65;41;38M65;41;38M35;39;36M35;34;34M

The terminal stays in this state until it is recreated or reset is run. clear does not help.

Root cause

When the server process goes away, the shell dies with it and no teardown output is written, so the persisted history for that terminal ends with whatever the app had enabled: Codex's Kitty push CSI > 7 u, Claude's mouse and focus tracking CSI ? 1003 h and CSI ? 1004 h. On the next open the server spawns a fresh shell on top of that history and the client replays it into a new libghostty instance, which then encodes releases, mouse reports, and focus reports for a shell that never asked for them.

Checked and ruled out:

  • Not history truncation. Both the server line cap and the client byte cap keep the tail, and a pop always follows its push, so truncation can drop a push but never orphan one.
  • Not alternate-screen TUIs. libghostty keeps separate Kitty stacks per screen, so a push inside the alternate screen (nvim, opencode) does not leak.
  • UI close, restart-in-place, and exit-then-reopen already reset history. First open after the server process is gone is the only path that inherits a dead process's history.

Confirmed by replaying the persisted log into the vendored ghostty-vt.wasm: with the dead Codex session's history a release of p encodes as ESC[112;1:3u; with a clean exit it encodes nothing.

Impact

Major degradation or frequent failure

Version or commit

0.0.38, also reproduced on main at 70cd258d

Environment

macOS 26, Apple Silicon, zsh, Codex CLI 0.152, Claude Code

Related
  • #8574 is the mouse half of this bug. The repro above reproduces it deterministically.
  • #4776 and #5319 covered replayed queries. This is replayed state, which #5319 keeps on purpose.
  • #9027 adds a DEC-mode neutralizer for the same scenario but does not cover the Kitty keyboard stack.
  • #4860 introduced the libghostty terminal and Kitty release encoding.
Workaround

Close and recreate the terminal, or run reset.

Filed by

Fable 5.1 via Claude Code, at the reporter's request.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in apps/server and trace how terminal history is persisted and replayed when the server process disappears, then inspect the vendored ghostty-vt.wasm behavior for Kitty keyboard and mouse state. Reproduce by replaying a dead Codex or Claude session versus a clean exit; done means reopening the terminal leaves zsh receiving plain input without key-release or mouse reports.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript, wasm
Domain
backend, cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.