can1357/oh-my-pi · error · Error

invalid key token ${token}

Error message

invalid key token ${token}

What it means

encodeChord() parses key tokens with optional C-/A-/M-/S- modifier prefixes. If after stripping modifiers nothing remains, or another modifier prefix is dangling (e.g. "C-" with no key), the token is malformed and this error is thrown.

Source

Thrown at packages/tui/src/debug-server.ts:135

function encodeChord(token: string): string {
	let rest = token;
	let ctrl = false;
	let alt = false;
	let shift = false;
	if (/^C-/i.test(rest)) {
		ctrl = true;
		rest = rest.slice(2);
	}
	if (/^(?:A|M)-/i.test(rest)) {
		alt = true;
		rest = rest.slice(2);
	}
	if (/^S-/i.test(rest)) {
		shift = true;
		rest = rest.slice(2);
	}
	if (rest.length === 0 || /^(?:C-|A-|M-|S-)/i.test(rest)) throw new Error(`invalid key token ${token}`);

	const name = rest.toLowerCase();
	const special = SPECIAL_KEYS[name];
	if (special !== undefined) {
		if (ctrl && name === "space") return alt ? "\x1b\x00" : "\x00";
		if (ctrl && name === "backspace") return alt ? "\x1b\x08" : "\x08";
		const modifier = 1 + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0);
		let encoded = modifiedSpecial(special, modifier);
		if (alt && encoded === special) encoded = `\x1b${encoded}`;
		return encoded;
	}
	if (Array.from(rest).length !== 1) throw new Error(`unknown key ${rest}`);
	let character = rest;
	if (shift && /^[a-z]$/i.test(character)) character = character.toUpperCase();
	if (ctrl) character = ctrlCharacter(character);
	return alt ? `\x1b${character}` : character;
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure each token has a non-empty base key after modifiers: "C-a", not "C-"
  2. Fix splitting logic so empty/whitespace tokens are filtered out
  3. Use single-letter or SPECIAL_KEYS names as the base key

Example fix

// before
const keys = "C-a,S-".split(",").map(encodeChord) // throws on "S-"
// after
const keys = "C-a,S-b".split(",").map(encodeChord)
Defensive patterns

Strategy: validation

Validate before calling

function isValidKeyToken(token: string): boolean {
  const rest = token.replace(/^(?:[CAM]-)+/i, "");
  return rest.length > 0 && !/^(?:C-|A-|M-|S-)/i.test(rest) &&
    (rest.length === 1 || rest.toLowerCase() in SPECIAL_KEYS);
}

Try / catch

try {
  tokens = parseKeyTokens(input);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("invalid key token")) {
    logger.warn("malformed key token in sequence", { input });
    tokens = [];
  } else throw err;
}

Prevention

When it happens

Trigger: Passing tokens like "", "C-", "A-", "C-A-S-" (empty base key), or a token where only modifier prefixes remain to the debug server key parser.

Common situations: String-splitting bugs leaving empty tokens (e.g. "a,,b" or trailing "+"); building key sequences programmatically with a trailing separator; typo'd sequences like "C--" intent.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/85686faf46f47451. Report an issue: GitHub.