kovidgoyal/kitty · warning

Unterminated string at end of input

Error message

Unterminated string at end of input

What it means

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.

Source

Thrown at tools/utils/shlex/shlex.go:161

				self.state = word
			case escape_char:
				self.write_escaped_ch()
			default:
				self.write_ch(ch)
			}
		}
	}
	switch self.state {
	case word:
		self.state = lex_normal
		if self.buf.Len() > 0 {
			return self.get_word()
		}
	case string_with_escapes, string_without_escapes:
		self.state = lex_normal
		ans.Trailer = self.buf.String()
		ans.Pos = self.word_start
		ans.Err = fmt.Errorf("Unterminated string at end of input")
		return
	case lex_normal:

	}
	return
}

// Split partitions a string into a slice of strings.
func Split(s string) (ans []string, err error) {
	l := NewLexer(s)
	var word Word
	for {
		word = l.Next()
		if word.Err != nil {
			return ans, word.Err
		}
		if word.Value == "" {
			break

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. Balance the quotes before splitting (append the missing matching quote).
  2. In completion flows, detect ans.Err for unterminated string and use ans.Trailer to continue reading input.
  3. Validate with a quote-parity check or regexp before calling Split.
  4. Prefer exec.Command argument slices over building shell strings when possible.

Example fix

// before
words, err := shlex.Split(cmdline)
// after
words, ans := shlex.SplitForCompletion(cmdline)
if ans.Err != nil { // unbalanced quote
    cmdline += `"` // or wait for more input in a REPL
    words, _ = shlex.Split(cmdline)
}
Defensive patterns

Strategy: fallback

Validate before calling

q := "'"
if strings.Count(s, "'")%2 == 1 || strings.Count(s, "\"")%2 == 1 {
    s += q // naive balance; prefer rejecting
}

Type guard

func quotesBalanced(s string) bool {
    return strings.Count(s, "'")%2 == 0 && strings.Count(s, `"`)%2 == 0
}

Try / catch

words, ans := shlex.SplitForCompletion(line)
if ans.Err != nil && strings.Contains(ans.Err.Error(), "Unterminated string") {
    words = append(words, ans.Trailer) // use partial word and continue reading
}

Prevention

When it happens

Trigger: 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.

Common situations: User typing interactively (completion splits partial input), quotes spanning truncated log lines, or nested-quote mistakes when building shell commands programmatically.

Related errors


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