python/cpython · error · KeySpecError

\C- followed by invalid key

Error message

\C- followed by invalid key

What it means

KeySpecError from _parse_single_key_sequence in Lib/_pyrepl/keymap.py. After the Ctrl modifier \C- is accepted, the remaining spec must resolve to exactly a single character or the named keys 'left'/'right'. Any other base key (multi-char name like 'home', 'f1', 'up') after \C- cannot be turned into a control byte and raises this error.

Source

Thrown at Lib/_pyrepl/keymap.py:189

                        % (ret, s + 2, repr(key))
                    )
                ret = _keynames[ret]
                s = t + 1
            else:
                raise KeySpecError(
                    "unknown backslash escape %s at char %d of %s"
                    % (repr(c), s + 2, repr(key))
                )
        else:
            ret = key[s]
            s += 1
    if ctrl:
        if len(ret) == 1:
            ret = chr(ord(ret) & 0x1F)  # curses.ascii.ctrl()
        elif ret in {"left", "right"}:
            ret = f"ctrl {ret}"
        else:
            raise KeySpecError("\\C- followed by invalid key")

    result = [ret], s
    if meta:
        result[0].insert(0, "\033")
    return result


def compile_keymap(keymap, empty=b""):
    r = {}
    for key, value in keymap.items():
        if isinstance(key, bytes):
            first = key[:1]
        else:
            first = key[0]
        r.setdefault(first, {})[key[1:]] = value
    for key, value in r.items():
        if empty in value:
            if len(value) != 1:

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Bind such keys without the Ctrl modifier: \<home>, \<f1> are addressable directly.
  2. For ctrl+arrow-style sequences, terminals send escape-prefixed codes — use the raw escape sequence (often \M- or the literal bytes your terminal emits, discoverable with read -r in a shell) instead of \C-.
  3. Restrict \C- to letters/digits/punctuation (single chars) and left/right only.

Example fix

# before
keymap = {r'\C-<home>': 'beginning-of-line'}

# after
keymap = {r'\<home>': 'beginning-of-line'}
Defensive patterns

Strategy: validation

Validate before calling

import re

def ctrl_target_valid(spec):
    # after removing \C- and \M-, the rest must be a single char or left/right
    body = re.sub(r'\\[cCmM]-', '', spec).strip('\\<>')
    return len(body) == 1 or body.lower() in {'left', 'right'}

Prevention

When it happens

Trigger: Specs like "\\C-<f1>" or "\\C-up" — i.e. \C- combined with any key that is not a single character and not left/right. The final block checks len(ret) == 1 or membership in {'left','right'} and otherwise raises.

Common situations: Trying to bind chord-style shortcuts to function keys (\C-<home>); assuming terminals transmit 'ctrl + arrow' as a single control byte; translated keymaps from GUI editors that freely combine Ctrl with any key.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/efca4cd4051d0b79. Report an issue: GitHub.