can1357/oh-my-pi · error

unterminated quoted key literal

Error message

unterminated quoted key literal

What it means

parseKeyTokens() supports quoted literals (e.g. "'abc'") that expand to per-character key events, with backslash escapes. If the closing quote never appears before end of input, the parser throws this error.

Source

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

	return alt ? `\x1b${character}` : character;
}

function parseKeyTokens(source: string): { sequences: string[]; events: number } {
	let offset = 0;
	const sequences: string[] = [];
	let events = 0;
	while (offset < source.length) {
		while (/\s/.test(source[offset] ?? "")) offset++;
		if (offset >= source.length) break;
		const quote = source[offset];
		if (quote === "'" || quote === '"') {
			offset++;
			let literal = "";
			while (offset < source.length && source[offset] !== quote) {
				if (source[offset] === "\\" && offset + 1 < source.length) offset++;
				literal += source[offset++];
			}
			if (source[offset] !== quote) throw new Error("unterminated quoted key literal");
			offset++;
			sequences.push(...Array.from(literal));
			events += Array.from(literal).length;
			continue;
		}
		const start = offset;
		while (offset < source.length && !/\s/.test(source[offset] ?? "")) offset++;
		sequences.push(encodeChord(source.slice(start, offset)));
		events++;
	}
	return { sequences, events };
}

function mouseSequence(x: number, y: number, action: string): string {
	if (!Number.isInteger(x) || !Number.isInteger(y) || x < 0 || y < 0)
		throw new Error("mouse coordinates must be non-negative integers");
	const at = (button: number, release = false): string => `\x1b[<${button};${x + 1};${y + 1}${release ? "m" : "M"}`;
	switch (action) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Close the quoted literal: "C-a,'hello'"
  2. Escape internal quotes with a backslash
  3. Check shell escaping if the sequence is passed via CLI — use single-quoted shell args or escape appropriately

Example fix

// before
parseKeyTokens("'hello") // unterminated
// after
parseKeyTokens("'hello'")
Defensive patterns

Strategy: validation

Validate before calling

function quotesBalanced(s: string): boolean {
  let i = 0;
  while (i < s.length) {
    const q = s[i];
    if (q === "'" || q === '"') {
      i++;
      while (i < s.length && s[i] !== q) { if (s[i] === "\\") i++; i++; }
      if (i >= s.length) return false;
    }
    i++;
  }
  return true;
}

Try / catch

try {
  tokens = parseKeyTokens(seq);
} catch (err) {
  if (err instanceof Error && err.message === "unterminated quoted key literal") {
    logger.warn("unterminated quote in key sequence", { seq });
    tokens = parseKeyTokens(`${seq}'`); // best-effort close
  } else throw err;
}

Prevention

When it happens

Trigger: Passing a key sequence containing an opening quote (' or ") without the matching close — e.g. "C-a,'hello".

Common situations: Shell quoting stripping the closing quote; hand-written sequences with an apostrophe intended as a literal but starting a quote; truncation of the sequence string.

Related errors


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