[BUG] Kitty keyboard protocol gated on terminal-name allow-list instead of CSI ? u capability — capable terminals (Alacritty) denied

Status Open
Reported on v2.1.193
Maintainer reply None cached
Activity 8 comments · opened Jun 26, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Environment

  • Claude Code: 2.1.193 (single-file compiled binary, embedded Bun 1.4.0)
  • Terminal: Alacritty 0.17.0
  • OS: Linux x86_64
  • Shell: fish

Summary

Claude Code decides whether to enable the kitty keyboard protocol by identifying the terminal by name against an allow-list, rather than by the terminal's capability reply to CSI ? u. Terminals that fully implement the kitty keyboard protocol but are not on the allow-list — and cannot be name-identified — are silently left in legacy input mode. Alacritty is the concrete case: Shift+Enter and Ctrl+Backspace do not work, even though Alacritty correctly speaks the protocol.

Mechanism (as documented in the now-mis-closed #27868, observed in v2.1.50, still current in 2.1.193):

The kitty keyboard push (CSI > 1 u) is gated on a terminal-name allow-list:

const kittyTerminals = ["iTerm.app", "kitty", "WezTerm", "ghostty"];
if (kittyTerminals.includes(detectedTerminal)) {
  stdout.write(kittyPushSequence); // CSI > 1 u
}

detectedTerminal comes from name/identity signals (TERM_PROGRAM, XTVERSION, KITTY_WINDOW_ID, $TERM). It is not derived from the CSI ? u capability response. So a terminal that proves protocol support via CSI ? u but whose name is not on the list never receives the push.

Why Alacritty specifically can never pass identity detection

Probing Alacritty 0.17.0 directly:

# XTVERSION (CSI > q) — terminal name/version
sent:  \x1b[>q
reply: \x1b[?6c                # only the primary-DA fence — NO DCS >| name reply

# kitty keyboard flags (CSI ? u) — capability
sent:  \x1b[?u
reply: \x1b[?0u\x1b[?6c        # CSI ? 0 u  => protocol SUPPORTED (flags currently 0)
  • Alacritty answers CSI ? u → the protocol is fully supported.
  • Alacritty does not answer XTVERSION, does not set TERM_PROGRAM, does not set KITTY_WINDOW_ID, and reports TERM=alacritty. So none of the identity channels can mark it kitty-capable.
  • Alacritty not answering XTVERSION is deliberate and permanent — the maintainer rejected it as wontfix (alacritty/alacritty#5273), even after a working PR, on the grounds that terminal-name/version detection is the wrong model for feature detection. So identity-based detection can never work for Alacritty by design; only capability-based detection can.

Proof that the only thing missing is identification, not capability

Launching with a faked kitty marker makes Claude send the push, and Alacritty honors it — Shift+Enter and Ctrl+Backspace immediately work:

env KITTY_WINDOW_ID=1 claude   # both keys now native in Alacritty

This confirms Alacritty fully honors the protocol flags once pushed; the push is being withheld purely on identity grounds.

What Should Happen?

When the terminal responds to CSI ? u with a valid CSI ? <flags> u reply, Claude Code should treat it as kitty-keyboard-capable and send the push sequence regardless of terminal name — i.e. detect the capability, not the identity. This matches the expected behavior already requested in #62423 ("When the terminal responds positively to the kitty keyboard protocol query (CSI ? u), Claude Code should use it"), extended to the case where the terminal is not on the name allow-list at all.

Capability-first detection also aligns with where the terminal ecosystem is deliberately heading (Alacritty's wontfix on XTVERSION is an explicit rejection of identity-based feature detection), and would fix not just Alacritty but any current/future spec-compliant terminal that isn't hard-coded into the list.

Suggested change (mirrors #27868's proposal, generalized): gate the push on the CSI ? u response, with the name allow-list and KITTY_WINDOW_ID kept only as fallbacks for terminals that don't answer the query.

Steps to Reproduce

  1. Open Alacritty (0.13+; tested 0.17.0) — confirm printf '\e[?u\e[c' returns a \e[?...u reply (protocol supported).
  2. Run claude normally (no env tweaks).
  3. Press Shift+Enter → submits instead of inserting a newline. Press Ctrl+Backspace → deletes one character instead of a word.
  4. Now run env KITTY_WINDOW_ID=1 claude in the same Alacritty → both keys work natively.

Additional Context

  • #27868 reported this exact identity-vs-capability gating and was auto-closed as a duplicate of #18135 — but #18135 is an unrelated JetBrains/JediTerm rendering bug closed as not-planned. The precise defect is therefore currently untracked.
  • Related: #62423 (legacy XTMODKEYS sent even when kitty protocol is advertised), #16066 (/terminal-setup writes the legacy Shift+Return binding for Alacritty), alacritty/alacritty#5273 (XTVERSION wontfix).

Is this a regression?

No — long-standing behavior of the identity-based detection path.

View original on GitHub ↗

5 Comments

github-actions[bot] · 2 months ago

Found 1 possible duplicate issue:

  1. https://github.com/anthropics/claude-code/issues/27868

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

severindupouy · 2 months ago

Not a duplicate to consolidate into. #27868 is the same root cause — that's intentional — but #27868 was itself auto-closed as a duplicate of #18135, which is an unrelated JetBrains/JediTerm rendering bug closed as not planned. So #27868 is dead and tracks nothing.

Closing #71700 as a duplicate of #27868 would re-bury a real, reproducible defect (capable terminals denied the kitty keyboard protocol because negotiation gates on terminal identity instead of the CSI ? u capability reply) with zero open tracking. Requesting it stay open. Happy to consolidate if #27868 is reopened instead — but it shouldn't be closed into a chain that terminates at a not-planned, unrelated issue.

btpence · 1 month ago

Another spec-compliant data point, with an angle this issue doesn't yet cover: over SSH there is no workaround at all.

AbsoluteTelnet/SSH implements the kitty keyboard protocol and answers CSI ? u with CSI ? <flags> u, but like Alacritty it isn't on the TERM_PROGRAM allow-list, so CSI > 1 u is never sent and Shift+Enter can't work.

The env KITTY_WINDOW_ID=1 claude workaround that rescues Alacritty locally is impossible for remote sessions. Claude reads that variable on the remote host, and SSH doesn't carry arbitrary env vars unless the server's AcceptEnv permits it (default is LANG LC_*). The one thing that always propagates — TERM — isn't checked. So for anyone running Claude over SSH in a capable-but-unlisted terminal, there is no client-side or user-side fix. Capability detection isn't just cleaner here; it's the only thing that can work.

Jimgitsit · 27 days ago

Another data point: iTerm2 is on the allow-list and still gets denied — over SSH.

Claude Code 2.1.220 both ends, iTerm2 3.6.11 → SSH → macOS remote.

Detection reads TERM_PROGRAM, which ssh does not forward:

local:  TERM_PROGRAM=iTerm.app  LC_TERMINAL=iTerm2
ssh:    TERM_PROGRAM=           LC_TERMINAL=iTerm2

LC_TERMINAL is never consulted, so over SSH the terminal is unidentifiable and Shift+Enter stops working. It is fully capable over that same connection:

printf '\e[>1u'; cat -v     # then Shift+Enter
^[[13;2u

Running iTerm2 directly on the remote host — same machine, same binary, same version — works, because TERM_PROGRAM is set there. One variable: whether the terminal's name survived the hop.

@btpence — there is an SSH workaround for iTerm2, since it also exports LC_TERMINAL and ssh forwards LC_*. On the remote, in ~/.zshrc:

if [ -z "$TERM_PROGRAM" ] && [ "$LC_TERMINAL" = "iTerm2" ]; then
  export TERM_PROGRAM=iTerm.app;
  export TERM_PROGRAM_VERSION="${LC_TERMINAL_VERSION:-3.6}";
fi

New session required. Alacritty has no equivalent marker, which is the point of this issue.

btpence · 27 days ago

I appreciate the fact that there are workarounds, as there often are.
But what's wrong with detection via capability probe?

Showing cached comments. Read the full discussion on GitHub ↗