[BUG] Shifted punctuation arrives unshifted in WezTerm (2.1.247 regression)

Status Open
Reported on v2.1.247
Maintainer reply None cached
Activity 12 comments · opened Aug 27, 2026

Since 2.1.247, shift+/ inserts / instead of ? in the prompt input under WezTerm. Shifted letters are fine, which is what makes it easy to miss for a while. 2.1.246 on the same machine and the same terminal is unaffected.

2.1.247 asks for kitty keyboard flags 5 where 2.1.246 asked for 1. Flag 4 requests alternate keys, so WezTerm now answers shift+/ with ESC [ 47:63;130u rather than a bare ? byte. 47 is /, 63 is the ? that was wanted, and 130 is shift with num lock on.

The decoder captures that middle field and then never reads it:

yh = /^\x1b\[(\d+)(?::(\d*)(?::(\d+))?)?(?:;(\d+))?u/
// group 1 = 47, group 2 = 63, group 3 = base layout key, group 4 = modifiers

let s = parseInt(l[1], 10)                              // 47
let m = l[3] ? parseInt(l[3], 10) : void 0
let h = f.ctrl && s > 127 && m !== void 0 ? m : s
return { name: Pb(f, h) ?? Ub(h), shift: f.shift, ... }

Text insertion then tries to re-derive the character from the name and the shift flag, by case mapping:

if (o.length === 1) {                        // one raw byte, the pre-2.1.247 path
  let l = o.charCodeAt(0)
  if (l >= 32 && l !== 127) return o         // '?' byte -> '?'
}
if (i) {
  if (e.shift && i.length === 1) {
    let l = i.toUpperCase()
    if (l !== i && l.length === 1) return l   // 'a' -> 'A'
  }
  return i                                    // '/' -> '/'
}

'a'.toUpperCase() is 'A', so letters come out right. '/'.toUpperCase() is still '/', the guard fails, and the unshifted character is returned. The 13-byte sequence also skips the single-byte path above, which is what handled this before. By that route every shifted key whose shifted form isn't an uppercase letter is affected, though ? is the only one I've checked.

To reproduce, set enable_kitty_keyboard = true in ~/.wezterm.lua (WezTerm defaults it off), open a prompt, and press shift+/. What the terminal sends can be confirmed outside Claude Code with the following, pressing shift+/ then Enter then Ctrl+D:

printf '\033[>5u'; od -c; printf '\033[<u\n'

Reading group 2 where it exists would fix it, as would requesting flag 16 so the terminal reports the associated text.

One caveat on attribution. My WezTerm is a nightly built the same day as 2.1.247, so I can't rule out movement on that side as well. What I did verify is the request, where >5u appears in the 2.1.247 binary and not in 2.1.246.

Environment:

  • Claude Code 2.1.247 (native install) broken, 2.1.246 fine
  • WezTerm 20260826-113711-78cd82db, enable_kitty_keyboard = true
  • Ubuntu 24.04.4 on WSL2, kernel 6.18.33.2-microsoft-standard-WSL2
  • TERM=wezterm, TERM_PROGRAM=WezTerm

View original on GitHub ↗

12 Comments

arnaudagent-droid · 1 month ago

Same regression on a different setup, with one extra data point: uppercase letters are lost here too, not only shifted punctuation.

  • Claude Code 2.1.248 (npm global) on native Windows 11 (not WSL) — TERM_PROGRAM=WezTerm, TERM=wezterm
  • WezTerm on Windows, enable_kitty_keyboard = true — kept on deliberately because Shift+Enter for a newline is needed, and on an AZERTY (French) layout the usual \+Enter fallback is an AltGr combo
  • Shift+letter yields the lowercase letter; Caps Lock is unaffected. Shifted punctuation is also wrong, consistent with the analysis above.
  • Reproduced on two separate Windows machines, both of which auto-updated 2.1.220 → 2.1.247/2.1.248 on 2026-08-27. 2.1.247 and 2.1.248 both affected.
  • Rolling back to a pre-2.1.247 build (2.1.220) restores correct behaviour; DISABLE_AUTOUPDATER=1 keeps it pinned.

So on this AZERTY / native-Windows path the "'a'.toUpperCase() saves letters" branch doesn't hold — the case-mapping guard appears to fail for letters as well. Looks like a regression of the earlier kitty-keyboard Shift+letter bugs (#39479, #49359), now re-triggered by the >5u flag change plus the unread group-2 field you identified.

I haven't yet captured the exact ESC [ … u sequence WezTerm emits for Shift+A on this layout — glad to, if it helps tell whether it's the base-layout-key field (group 3) or the modifier value that differs from your 47:63;130u capture.

staskorz · 1 month ago

The issue still persists in Claude Code 2.1.250.
Environment: Kubuntu 24.04 x64
WezTerm: 20240203-110809-5046fc22 (stable)

RTRowe · 1 month ago

Another native-Windows data point, and one that doesn't fit the "every shifted key whose shifted form isn't an uppercase letter is affected" scope in the OP.

Environment

  • Claude Code 2.1.251 (native install) — one version newer than the last confirmation in this thread
  • WezTerm 20260714-220616-d96ba571, enable_kitty_keyboard = true
  • Windows 11 Pro 26200, native (not WSL), PowerShell 7
  • TERM_PROGRAM=WezTerm, TERM=xterm-256color (note: not wezterm, unlike the OP)

Only ? is affected here. Shift+1 → ! and Shift+; → : both insert correctly in the prompt, and capital letters are fine. Only Shift+/ yields /.

That's worth flagging, because the terminal encodes all three identically. Captured with a raw-stdin reader in the same WezTerm, pushing the same flags Claude Code asks for:

[A] LEGACY (no flags pushed)
    SHIFT + / :   3f    ?
    SHIFT + 1 :   21    !
    SHIFT + ; :   3a    :

[B] flags >1u  (what 2.1.246 asked for)
    SHIFT + / :   3f    ?
    SHIFT + 1 :   21    !
    SHIFT + ; :   3a    :

[C] flags >5u  (what 2.1.247+ asks for)
    SHIFT + / :   1b 5b 34 37 3a 36 33 3b 31 33 30 75    ESC[47:63;130u
    SHIFT + 1 :   1b 5b 34 39 3a 33 33 3b 31 33 30 75    ESC[49:33;130u
    SHIFT + ; :   1b 5b 35 39 3a 35 38 3b 31 33 30 75    ESC[59:58;130u

So WezTerm reports base:shifted for all three — 47:63, 49:33, 59:58 — and Claude Code recovers ! and : correctly but not ?. Whatever drops the group-2 field looks narrower than a blanket "unshifted character returned". It may be worth checking whether / takes a different path from the other two, given it's also the slash-command trigger, rather than assuming the toUpperCase fallback accounts for all of it.

Between the three reports there are now three distinct scopes: OP (shifted punctuation, letters fine), @arnaudagent-droid (shifted punctuation and uppercase letters lost, AZERTY), and this one (only ?). Same encoding, different outcomes.

The OP's workaround is confirmed — commenting out enable_kitty_keyboard restores ?. One detail that saves a restart: it takes effect on WezTerm's config hot-reload, with no restart of WezTerm or of the running Claude Code session, because the setting gates whether WezTerm honors the pushed flags at all.

---

@arnaudagent-droid — you mentioned keeping the protocol enabled because you need Shift+Enter and the \+Enter fallback is an AltGr combo on AZERTY. Two ways to get the newline without the protocol:

  • Ctrl+J inserts a newline in every terminal, no config and no AltGr.
  • Or bind it in WezTerm, which is what I'm now running:

``lua
{ key = 'Enter', mods = 'SHIFT', action = wezterm.action.SendString '\n' },
``

Claude Code decodes CR (0x0d) as return → submit and LF (0x0a) as enter → newline, so sending a bare LF gives a line break with no kitty protocol involved.

Worth noting /terminal-setup isn't an option for either of us — the docs list only VS Code, Cursor, Devin Desktop, Alacritty and Zed; WezTerm is in the "works without setup" column precisely because it normally speaks this protocol.

---

One trap for anyone reproducing this: probe with the same flags Claude Code actually sends. My first capture used >1u and returned a clean literal ?, which wrongly exonerated the terminal and cost me a fair bit of time. The OP's probe is the correct one:

printf '\033[>5u'; od -c; printf '\033[<u\n'
cebor · 1 month ago

Issue still in v2.1.252.

And for me all "Shift + {something}" do not work.

Shift+1 : does not work
Shift+-: does not work
Shift+Enter: does not work

Using a QWERTZ Keyboard with config.enable_kitty_keyboard = true.

staskorz · 1 month ago

Workaround - add the following to your ~/.wezterm.lua:

-- Workaround for anthropics/claude-code#90067: since Claude Code 2.1.247 it
-- asks for kitty keyboard flag 5 (disambiguate + report alternate keys), then
-- ignores the alternate (shifted) code wezterm reports and re-derives the
-- character with toUpperCase(), which is a no-op for punctuation - so Shift+/
-- arrives as '/', Shift+1 as '1', and so on. SendString writes the literal
-- byte straight to the pty, bypassing the kitty encoder for these keys only.
local SHIFTED_PUNCTUATION = {
  '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
  '_', '+', '{', '}', '|', ':', '"', '<', '>', '?',
}

for _, ch in ipairs(SHIFTED_PUNCTUATION) do
  -- wezterm may report the shifted glyph with or without SHIFT in mods
  -- (its own defaults register both forms), so bind both.
  table.insert(config.keys, { key = ch, mods = 'NONE',  action = wezterm.action.SendString(ch) })
  table.insert(config.keys, { key = ch, mods = 'SHIFT', action = wezterm.action.SendString(ch) })
end

I'm using it for a few days already, didn't notice any regressions, other apps keep working fine.

dobsontom · 1 month ago

Running @staskorz's workaround here on 2.1.252, and it does the job, thanks for posting it. It can be halved though. The mods = 'NONE' bindings never fire for punctuation, so only the SHIFT half is doing anything.

wezterm normalises the shift modifier on both sides of the lookup, in normalize_shift (wezterm-input-types/src/lib.rs). That only rewrites KeyCode::Char(c) where c is an ASCII letter, and everything else falls through its _ => (key, modifiers) arm with SHIFT intact. So shift+/ arrives as ('?', SHIFT) and matches the SHIFT entry only. Config::key_bindings puts the configured side through the same function, so {key = '?', mods = 'NONE'} is stored under ('?', NONE) and nothing ever reaches it. wezterm's own docs bind shifted punctuation the same way, key = '|', mods = 'LEADER|SHIFT'.

It flips for letters. Maybe useful for @arnaudagent-droid and @cebor, since you're both losing capitals on top of the punctuation. A lowercase char plus SHIFT gets rewritten to the uppercase char with SHIFT removed, so the binding wants the uppercase key:

{ key = 'A', mods = 'SHIFT', action = wezterm.action.SendString 'A' },

Either mods form works there, since both sides normalise to the same pair. Won't cover anything reached by AltGr on AZERTY though, that arrives with different mods and neither binding matches.

0xGREG · 1 month ago

I created a repo that patches the claude binary to fix the issue https://github.com/0xGREG/claude-kitty-patch

dobsontom · 1 month ago
cebor · 29 days ago

For me this issue is not resolved. Still in 2.1.272.

With config.enable_kitty_keyboard = true it does not work.

dobsontom · 29 days ago

@cebor OK, I've reopened the issue for your case :)

arnaudagent-droid · 27 days ago

Confirming the regression is still present after the 2.1.269 fix, on a distinct key subset from both the OP and RTRowe.

Environment: Claude Code 2.1.270 (npm global), Windows 11 native (not WSL), WezTerm 20240203-110809-5046fc22, enable_kitty_keyboard = true, AZERTY (French) layout.

Still broken: Shift+letter yields lowercase, no uppercase at all (same as my Aug 28 report on 2.1.248). Shifted punctuation: ) = , * ù <.
Fine: . / § with Shift. Digits (Shift+top-row on AZERTY) unaffected.

Between the reports there are now at least four distinct scopes on the same encoding: OP (punctuation only, letters fine), mine (punctuation + all letters, AZERTY), RTRowe (only Shift+/), cebor (QWERTZ, broadly broken). The 2.1.269 fix clearly didn't close all of these. Rolling back to 2.1.220 again as a stopgap.

0xGREG · 27 days ago
Confirming the regression is still present after the 2.1.269 fix, on a distinct key subset from both the OP and RTRowe. Environment: Claude Code 2.1.270 (npm global), Windows 11 native (not WSL), WezTerm 20240203-110809-5046fc22, enable_kitty_keyboard = true, AZERTY (French) layout. Still broken: Shift+letter yields lowercase, no uppercase at all (same as my Aug 28 report on 2.1.248). Shifted punctuation: ) = , * ù <. Fine: . / § with Shift. Digits (Shift+top-row on AZERTY) unaffected. Between the reports there are now at least four distinct scopes on the same encoding: OP (punctuation only, letters fine), mine (punctuation + all letters, AZERTY), RTRowe (only Shift+/), cebor (QWERTZ, broadly broken). The 2.1.269 fix clearly didn't close all of these. Rolling back to 2.1.220 again as a stopgap.

This is a bug in the kitty protocol implementation of WezTerm, WezTerm nightly resolves the issue. Otherwise use my patcher https://github.com/0xGREG/claude-kitty-patch that uses legacy kitty protocol.