python/cpython · error · KeySpecError

unterminated \< starting at char %d of %s

Error message

unterminated \< starting at char %d of %s

What it means

KeySpecError from _parse_single_key_sequence in Lib/_pyrepl/keymap.py. The \< escape opens a named-key reference that must be closed with '>' (e.g. \<up>, \<f1>). If no '>' is found anywhere after the \<, the spec is unterminated and this error is raised with the 1-based start position of the \< token.

Source

Thrown at Lib/_pyrepl/keymap.py:163

                    )
                if meta:
                    raise KeySpecError(
                        "doubled \\M- (char %d of %s)" % (s + 1, repr(key))
                    )
                meta = 1
                s += 3
            elif c.isdigit():
                n = key[s + 1 : s + 4]
                ret = chr(int(n, 8))
                s += 4
            elif c == "x":
                n = key[s + 2 : s + 4]
                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]

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Close the named key: \<up> not \<up>.
  2. Check the exact keyname against _keynames in _pyrepl/keymap.py (case-insensitive) — wrong names give a different error but truncated ones give this.
  3. Lint every custom spec through _parse_single_key_sequence at startup.

Example fix

# before
keymap = {r'\<up': 'previous-history'}

# after
keymap = {r'\<up>': 'previous-history'}
Defensive patterns

Strategy: validation

Validate before calling

def closed_keyname(spec):
    i = spec.find('\\<')
    return i == -1 or spec.find('>', i) != -1

Prevention

When it happens

Trigger: Key specs containing \< with no closing '>', e.g. "\\<up", "\\<home" or a truncated spec string. key.find('>', s) returning -1 is the exact trigger.

Common situations: Spec strings cut off by shell quoting or line wrapping; typos like \<up<<; copying specs from docs where the closing '>' was dropped; f-string interpolation accidentally consuming the bracket.

Related errors


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