kovidgoyal/kitty · error · ValueError

Native key codes not allowed in send_key: {human_key}

Error message

Native key codes not allowed in send_key: {human_key}

What it means

Window.send_key() parses each human-readable key with parse_shortcut() and refuses shortcuts whose key is a native key code (sk.is_native). send_key synthesizes GLFW key events, so platform-native codes cannot be mapped; use symbolic key names instead. The check happens per argument before any events are queued.

Source

Thrown at kitty/window.py:1247

        Note that the key will be sent only if the current keyboard mode of the program running in the terminal supports it.
        Both key press and key release are sent. First presses for all specified keys and then releases in reverse order.
        To send a pattern of press and release for multiple keys use the :ac:`combine` action. For example::

            map f1 send_key ctrl+x alt+y
            map f1 combine : send_key ctrl+x : send_key alt+y
    """,
    )
    def send_key(self, *args: str) -> bool:
        from .options.utils import parse_shortcut

        km = get_options().kitty_mod
        passthrough = True
        events = []
        prev = ''
        for human_key in args:
            sk = parse_shortcut(human_key)
            if sk.is_native:
                raise ValueError(f'Native key codes not allowed in send_key: {human_key}')
            sk = sk.resolve_kitty_mod(km)
            events.append(KeyEvent(key=sk.key, mods=sk.mods, action=GLFW_REPEAT if human_key == prev else GLFW_PRESS))
            prev = human_key
        scroll_needed = False
        for ev in events + [KeyEvent(key=x.key, mods=x.mods, action=GLFW_RELEASE) for x in reversed(events)]:
            enc = self.encoded_key(ev)
            if enc:
                self.write_to_child(enc)
                if ev.action != GLFW_RELEASE and not is_modifier_key(ev.key):
                    scroll_needed = True
                passthrough = False
        if scroll_needed:
            self.scroll_end()
        return passthrough

    def send_key_sequence(self, *keys: KeyEvent, synthesize_release_events: bool = True) -> None:
        for key in keys:
            enc = self.encoded_key(key)

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. Use symbolic names: send_key('ctrl+c'), send_key('enter'), send_key('f1')
  2. If you only need text input, use send_text / @ send-text which doesn't parse keys
  3. Find valid names via kitten show-key

Example fix

# before
window.send_key('native:65')
# after
window.send_key('space')
Defensive patterns

Strategy: validation

Validate before calling

from kitty.key_encoding import parse_shortcut
sk = parse_shortcut(human_key)
if sk.is_native:
    raise ValueError(f'use a symbolic key name, not {human_key}')

Type guard

def is_symbolic_key(human_key: str) -> bool:
    from kitty.key_encoding import parse_shortcut
    return not parse_shortcut(human_key).is_native

Try / catch

try:
    window.send_key(*keys)
except ValueError as e:
    if 'Native key codes' in str(e):
        window.send_text(' '.join(keys))  # fallback: text input
    else:
        raise

Prevention

When it happens

Trigger: Calling send_key('native:0x20') or any shortcut string whose key part resolves to a native code rather than a GLFW/symbolic name, e.g. send_key('native_key_here').

Common situations: Feeding platform scan codes or keysym numbers obtained from other tools into send_key; remote-control scripts (kitten @ send-key) passing machine-level codes; confusing send_key with the escape-code based send_text.


AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27). Data as JSON: /api/errors/348dfd108baac55c. Report an issue: GitHub.