anthropics / anthropics/claude-code

[BUG] AltGr characters are lost in Windows Terminal since the kitty keyboard protocol is enabled unconditionally

Open
#94,873 0 comments 0 reactions 0 assignees View on GitHub
area:tui bug has repro keybindings platform:windows regression
Dominant language
Python
Stars
145k
Forks
23.1k
PR merge metrics
PR metrics pending

Description

### Preflight Checklist

- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) 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?

# AltGr characters are lost in Windows Terminal since the kitty keyboard protocol is enabled unconditionally

## Environment

| | |
|---|---|
| Claude Code | 2.1.273 (broken); 2.1.270 and 2.1.272 contain the same code; **2.1.124 works** |
| Install | native Windows binary, `%USERPROFILE%\.local\bin\claude.exe` |
| Terminal | Windows Terminal 1.25.1912.0 |
| OS | Windows 11 Pro 26200 |
| Keyboard | French AZERTY (any layout that uses AltGr is affected) |

## Summary

On Windows, AltGr is physically reported as Ctrl+Alt. When Claude Code enables the kitty
keyboard protocol, Windows Terminal reports AltGr key presses as CSI u sequences carrying
the **alt** modifier and the *base* key, instead of delivering the character produced by
the keyboard layout. Claude Code's AltGr fallback never fires, and the character is lost.

On a French AZERTY layout this silently kills every character on the third level:
`€ # { [ | \ ] }`. Only the combinations whose base key is non-ASCII (`à`, `é`, `è`, `ç`)
still work, because the terminal does not disambiguate those.

The same `claude.exe` run from a WSL shell in the same Windows Terminal window is not
affected: there the byte stream comes from the Linux pty, which never requested the
protocol.

## Measurement

A small Win32 program puts `CONIN$` in `ENABLE_VIRTUAL_TERMINAL_INPUT`, forces the console
code pages to 65001, emits a given mode sequence, and dumps the raw bytes received.
Key presses: AltGr+E (`€`), AltGr+8 (`\`), AltGr+0 (`@`), AltGr+6 (`|`), plain E.

| Mode requested | AltGr+E | AltGr+8 | AltGr+0 | AltGr+6 | E |
|---|---|---|---|---|---|
| none | `E2 82 AC` ✅ | `\` ✅ | `@` ✅ | `|` ✅ | `E` |
| `ESC[>4;2m` | `E2 82 AC` ✅ | `\` ✅ | `@` ✅ | `|` ✅ | `E` |
| `ESC[5u` | `ESC[101;3u` ❌ | `ESC[95::56;3u` ❌ | `@` ✅ | `ESC[45::54;3u` ❌ | `E` |
| `ESC[>1u` | `ESC[101;3u` ❌ | `ESC[95;3u` ❌ | `@` ✅ | `ESC[45;3u` ❌ | `E` |
| `ESC[>0u` | `E2 82 AC` ✅ | `\` ✅ | `@` ✅ | `|` ✅ | `E` |

Conclusions:

- `modifyOtherKeys` (`ESC[>4;2m`) is harmless.
- The **disambiguation flag alone** (`>1u`) already breaks AltGr; flag 4 only adds the
alternate-key sub-parameters. So this is not about "report alternate keys".
- With flags `0`, everything works — which is the behaviour Claude Code had before.
- Note the modifier: `;3u` decodes to `3 - 1 = 2`, i.e. **alt only**. Windows Terminal
strips the Ctrl half of AltGr, so the combination never looks like Ctrl+Alt to the
application.

## Root cause

Three things combine.

**1. The capability is forced on for Windows Terminal, with no way out.**
The terminal table used to settle `extendedKeys` contains `"windows-terminal"`, and the
resolver returns `{settled: true}` for any terminal in that table — no probe, no
environment override. `process.env.WT_SESSION` is what makes the terminal resolve to
`"windows-terminal"`.

**2. Clearing `WT_SESSION` does not help**, because the capability probe then runs and
Windows Terminal answers `ESC[?0u` to `ESC[?u`, which settles `extendedKeys` to true
anyway (`answer("extendedKeys", true, "probe: …")`).

**3. The AltGr fallback cannot fire.** The guard starts with

```js
if (!(key.ctrl && key.meta) || key.super) return false;
```

and only consults `CLAUDE_CODE_ALTGR_AS_TEXT` *after* that test. Since Windows Terminal
reports AltGr with the alt modifier only, `key.ctrl` is false and the function returns
immediately. The escape hatch documented for exactly this situation therefore has no
effect — confirmed empirically: setting `CLAUDE_CODE_ALTGR_AS_TEXT=1` changes nothing.

Even if the guard were reached, the branch builds the character with
`String.fromCodePoint(code)` where `code` is the **key** code (101 = `e`), not the produced
character (8364 = `€`). Recovering the real character would require kitty flag 16 (*report
associated text*), which is not requested — the pushed flag set is 5 (1 + 4).

## Regression

Version 2.1.124 emits the same kind of sequence but never reaches that code path under
Windows Terminal: it contains an explicit guard,

```js
if (process.env.WT_SESSION) return false;
```

That guard is gone in 2.1.273, while the terminal table is unchanged — it still lists
`"windows-terminal"` in both versions. So the regression is the removal of the Windows
Terminal exclusion, not a change in the terminal list or in terminal detection.

## Suggested fixes (any one of them)

1. **Restore the Windows Terminal exclusion** for the extended-keys capability, or push
flags `0` there. Least risk, restores 2.1.124 behaviour exactly.
2. **Relax the AltGr guard** to also accept a key carrying the alt modifier alone on
Windows, so `CLAUDE_CODE_ALTGR_AS_TEXT` becomes usable — and honour the variable
*before* the modifier test, so users have a working escape hatch.
3. **Request flag 16** (*report associated text*) alongside the current flags, and parse
the third CSI u parameter. Note the current regex stops at the modifier group, so
`ESC[101;3;8364u` would not match today:

```
/^\x1b\[(\d+)(?::(\d*)(?::(\d+))?)?(?:;(\d+))?u/
```

4. At minimum, **provide an environment variable that disables the extended-keys
capability**. Today there is none: `CLAUDE_CODE_SESSION_KIND=bg` is the only value that
settles it to false, and it changes far too much else to be usable interactively.

Fix 1 or 2 would benefit every AltGr layout — French, German, Spanish, Polish, Nordic —
not just this one.

## Reproduction

1. Windows Terminal, native Windows Claude Code 2.1.273, any layout with an AltGr level.
2. Start `claude` and press AltGr+E on a French AZERTY keyboard.
3. Expected: `€` is inserted. Actual: nothing is inserted.
4. `CLAUDE_CODE_ALTGR_AS_TEXT=1` makes no difference.
5. The same binary launched from a WSL shell in the same window behaves correctly.

### What Should Happen?

Pressing AltGr+E on a French AZERTY keyboard should insert "€" into the prompt.
More generally, every AltGr combination should insert the character produced by the
keyboard layout: € # { [ | \ ] } on AZERTY, and the equivalent third-level characters
on any other layout that uses AltGr (German, Spanish, Polish, Nordic).

This is what Claude Code 2.1.124 does in the same Windows Terminal window, and what
2.1.273 itself still does when launched from a WSL shell in that same window.

Additionally, CLAUDE_CODE_ALTGR_AS_TEXT=1 should have an observable effect. It is the
documented escape hatch for this exact situation, but it currently changes nothing
because it is only consulted after a `key.ctrl && key.meta` test that AltGr never
satisfies on Windows Terminal.

### Error Messages/Logs

```shell
None.
```

### Steps to Reproduce

Launch claude.exe, then press AltGr+E on a French AZERTY keyboard.

### Claude Model

Opus

### Is this a regression?

Yes, this worked in a previous version

### Last Working Version

2.1.124

### Claude Code Version

2.1.273

### Platform

Other

### Operating System

Windows

### Terminal/Shell

Windows Terminal

### Additional Information

_No response_

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reproducing AltGr+E in native Windows Claude Code under Windows Terminal, then inspect the extendedKeys terminal table and resolver, followed by the AltGr fallback. Done means AltGr characters such as € are inserted, the documented CLAUDE_CODE_ALTGR_AS_TEXT escape hatch has an observable effect, and behavior remains correct in WSL.

Written by the indexing model from the issue text.

Assessment

Domain
cli, operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.