{"record":{"id":"fbb099569c5412d4","repo":"kovidgoyal/kitty","slug":"unterminated-string-at-end-of-input","errorCode":null,"errorMessage":"Unterminated string at end of input","messagePattern":"Unterminated string at end of input","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"tools/utils/shlex/shlex.go","lineNumber":161,"sourceCode":"\t\t\t\tself.state = word\n\t\t\tcase escape_char:\n\t\t\t\tself.write_escaped_ch()\n\t\t\tdefault:\n\t\t\t\tself.write_ch(ch)\n\t\t\t}\n\t\t}\n\t}\n\tswitch self.state {\n\tcase word:\n\t\tself.state = lex_normal\n\t\tif self.buf.Len() > 0 {\n\t\t\treturn self.get_word()\n\t\t}\n\tcase string_with_escapes, string_without_escapes:\n\t\tself.state = lex_normal\n\t\tans.Trailer = self.buf.String()\n\t\tans.Pos = self.word_start\n\t\tans.Err = fmt.Errorf(\"Unterminated string at end of input\")\n\t\treturn\n\tcase lex_normal:\n\n\t}\n\treturn\n}\n\n// Split partitions a string into a slice of strings.\nfunc Split(s string) (ans []string, err error) {\n\tl := NewLexer(s)\n\tvar word Word\n\tfor {\n\t\tword = l.Next()\n\t\tif word.Err != nil {\n\t\t\treturn ans, word.Err\n\t\t}\n\t\tif word.Value == \"\" {\n\t\t\tbreak","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/tools/utils/shlex/shlex.go#L143-L179","documentation":"The shlex lexer reached end of input while still inside a single- or double-quoted string; the closing quote never appeared. The accumulated partial word is exposed as ans.Trailer for completion use.","triggerScenarios":"Split(\"echo 'hello world\") or Split(\"git commit -m \\\"wip\") — any unbalanced quote. Hit from the string_with_escapes/string_without_escapes states at EOF.","commonSituations":"User typing interactively (completion splits partial input), quotes spanning truncated log lines, or nested-quote mistakes when building shell commands programmatically.","solutions":["Balance the quotes before splitting (append the missing matching quote).","In completion flows, detect ans.Err for unterminated string and use ans.Trailer to continue reading input.","Validate with a quote-parity check or regexp before calling Split.","Prefer exec.Command argument slices over building shell strings when possible."],"exampleFix":"// before\nwords, err := shlex.Split(cmdline)\n// after\nwords, ans := shlex.SplitForCompletion(cmdline)\nif ans.Err != nil { // unbalanced quote\n    cmdline += `\"` // or wait for more input in a REPL\n    words, _ = shlex.Split(cmdline)\n}","handlingStrategy":"fallback","validationCode":"q := \"'\"\nif strings.Count(s, \"'\")%2 == 1 || strings.Count(s, \"\\\"\")%2 == 1 {\n    s += q // naive balance; prefer rejecting\n}","typeGuard":"func quotesBalanced(s string) bool {\n    return strings.Count(s, \"'\")%2 == 0 && strings.Count(s, `\"`)%2 == 0\n}","tryCatchPattern":"words, ans := shlex.SplitForCompletion(line)\nif ans.Err != nil && strings.Contains(ans.Err.Error(), \"Unterminated string\") {\n    words = append(words, ans.Trailer) // use partial word and continue reading\n}","preventionTips":["Use SplitForCompletion (not Split) for partial user input.","Continue reading input when ans.Trailer is set and a quote is open.","Prefer exec.Command slices over shell strings."],"tags":["go","shlex","shell-parsing","quoting"],"backgroundTag":"unterminated-shell-token","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}