python/cpython · error · KeySpecError

unknown backslash escape %s at char %d of %s

Error message

unknown backslash escape %s at char %d of %s

What it means

KeySpecError from _parse_single_key_sequence in Lib/_pyrepl/keymap.py. After a backslash, the next character must be one of the recognized escapes: _escapes entries, 'c' (\C-), 'm' (\M-), octal digits, 'x' (hex), or '<' (named key). Anything else is an unknown backslash escape and raises this error showing the offending character.

Source

Thrown at Lib/_pyrepl/keymap.py:176

                ret = chr(int(n, 16))
                s += 4
            elif c == "<":
                t = key.find(">", s)
                if t == -1:
                    raise KeySpecError(
                        "unterminated \\< starting at char %d of %s"
                        % (s + 1, repr(key))
                    )
                ret = key[s + 2 : t].lower()
                if ret not in _keynames:
                    raise KeySpecError(
                        "unrecognised keyname `%s' at char %d of %s"
                        % (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

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Check the _escapes dict at the top of Lib/_pyrepl/keymap.py and use only its escape letters.
  2. For control/meta use \C- and \M-; for named keys use \<name>; for arbitrary chars use octal (\NNN) or hex (\xNN) forms.
  3. Strip/escape stray backslashes from dynamically built spec strings before parsing.

Example fix

# before
keymap = {r'\z': 'foo'}

# after
# use hex escape for the literal char, e.g. Ctrl-Z is \C-z or \x1a
keymap = {r'\C-z': 'foo'}
Defensive patterns

Strategy: validation

Validate before calling

from _pyrepl.keymap import _escapes, _keynames

def only_known_escapes(spec):
    import re
    for m in re.finditer(r'\\(.)', spec):
        c = m.group(1).lower()
        if c not in _escapes and c not in 'cmx<' and not c.isdigit():
            return False
    return True

Prevention

When it happens

Trigger: Specs containing an escape the parser does not know, e.g. "\\e" is fine (in _escapes) but "\\z", "\\t" only if 't' is in _escapes, or Windows-style "\\\\" handling mistakes; also single trailing backslash where key[s+1] indexing grabs a wrong char.

Common situations: Porting readline init syntax (which supports more escapes) into pyrepl keymaps; platform path-separator backslashes leaking into key specs; assuming \n/\r/\t spellings without checking _escapes in the module.

Related errors


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