{"record":{"id":"1fc7cc863af17eb3","repo":"can1357/oh-my-pi","slug":"unterminated-quoted-key-literal","errorCode":null,"errorMessage":"unterminated quoted key literal","messagePattern":"unterminated quoted key literal","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/tui/src/debug-server.ts","lineNumber":169,"sourceCode":"\treturn alt ? `\\x1b${character}` : character;\n}\n\nfunction parseKeyTokens(source: string): { sequences: string[]; events: number } {\n\tlet offset = 0;\n\tconst sequences: string[] = [];\n\tlet events = 0;\n\twhile (offset < source.length) {\n\t\twhile (/\\s/.test(source[offset] ?? \"\")) offset++;\n\t\tif (offset >= source.length) break;\n\t\tconst quote = source[offset];\n\t\tif (quote === \"'\" || quote === '\"') {\n\t\t\toffset++;\n\t\t\tlet literal = \"\";\n\t\t\twhile (offset < source.length && source[offset] !== quote) {\n\t\t\t\tif (source[offset] === \"\\\\\" && offset + 1 < source.length) offset++;\n\t\t\t\tliteral += source[offset++];\n\t\t\t}\n\t\t\tif (source[offset] !== quote) throw new Error(\"unterminated quoted key literal\");\n\t\t\toffset++;\n\t\t\tsequences.push(...Array.from(literal));\n\t\t\tevents += Array.from(literal).length;\n\t\t\tcontinue;\n\t\t}\n\t\tconst start = offset;\n\t\twhile (offset < source.length && !/\\s/.test(source[offset] ?? \"\")) offset++;\n\t\tsequences.push(encodeChord(source.slice(start, offset)));\n\t\tevents++;\n\t}\n\treturn { sequences, events };\n}\n\nfunction mouseSequence(x: number, y: number, action: string): string {\n\tif (!Number.isInteger(x) || !Number.isInteger(y) || x < 0 || y < 0)\n\t\tthrow new Error(\"mouse coordinates must be non-negative integers\");\n\tconst at = (button: number, release = false): string => `\\x1b[<${button};${x + 1};${y + 1}${release ? \"m\" : \"M\"}`;\n\tswitch (action) {","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/tui/src/debug-server.ts#L151-L187","documentation":"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.","triggerScenarios":"Passing a key sequence containing an opening quote (' or \") without the matching close — e.g. \"C-a,'hello\".","commonSituations":"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.","solutions":["Close the quoted literal: \"C-a,'hello'\"","Escape internal quotes with a backslash","Check shell escaping if the sequence is passed via CLI — use single-quoted shell args or escape appropriately"],"exampleFix":"// before\nparseKeyTokens(\"'hello\") // unterminated\n// after\nparseKeyTokens(\"'hello'\")","handlingStrategy":"validation","validationCode":"function quotesBalanced(s: string): boolean {\n  let i = 0;\n  while (i < s.length) {\n    const q = s[i];\n    if (q === \"'\" || q === '\"') {\n      i++;\n      while (i < s.length && s[i] !== q) { if (s[i] === \"\\\\\") i++; i++; }\n      if (i >= s.length) return false;\n    }\n    i++;\n  }\n  return true;\n}","typeGuard":null,"tryCatchPattern":"try {\n  tokens = parseKeyTokens(seq);\n} catch (err) {\n  if (err instanceof Error && err.message === \"unterminated quoted key literal\") {\n    logger.warn(\"unterminated quote in key sequence\", { seq });\n    tokens = parseKeyTokens(`${seq}'`); // best-effort close\n  } else throw err;\n}","preventionTips":["Always close quoted literals in key sequences","Escape literal quotes/backslashes with backslash","Be careful with shell quoting when passing sequences via CLI"],"tags":["parsing","keyboard-input","escaping"],"backgroundTag":"unterminated-string-literal","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}