{"record":{"id":"08833019b7f11a1c","repo":"gastownhall/beads","slug":"unterminated-string-starting-at-position-d","errorCode":null,"errorMessage":"unterminated string starting at position %d","messagePattern":"unterminated string starting at position (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/query/lexer.go","lineNumber":201,"sourceCode":"\t\tif unicode.IsDigit(r) || r == '-' || r == '+' {\n\t\t\tl.backup()\n\t\t\treturn l.readNumberOrDuration(startPos)\n\t\t}\n\t\tif isIdentStart(r) {\n\t\t\tl.backup()\n\t\t\treturn l.readIdent(startPos)\n\t\t}\n\t\treturn Token{}, fmt.Errorf(\"unexpected character %q at position %d\", r, startPos)\n\t}\n}\n\n// readString reads a quoted string.\nfunc (l *Lexer) readString(quote rune, startPos int) (Token, error) {\n\tvar sb strings.Builder\n\tfor {\n\t\tr := l.next()\n\t\tif r == 0 {\n\t\t\treturn Token{}, fmt.Errorf(\"unterminated string starting at position %d\", startPos)\n\t\t}\n\t\tif r == quote {\n\t\t\treturn Token{Type: TokenString, Value: sb.String(), Pos: startPos}, nil\n\t\t}\n\t\tif r == '\\\\' {\n\t\t\t// Handle escape sequences\n\t\t\tescaped := l.next()\n\t\t\tswitch escaped {\n\t\t\tcase 'n':\n\t\t\t\tsb.WriteRune('\\n')\n\t\t\tcase 't':\n\t\t\t\tsb.WriteRune('\\t')\n\t\t\tcase '\\\\':\n\t\t\t\tsb.WriteRune('\\\\')\n\t\t\tcase '\"':\n\t\t\t\tsb.WriteRune('\"')\n\t\t\tcase '\\'':\n\t\t\t\tsb.WriteRune('\\'')","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/query/lexer.go#L183-L219","documentation":"A quoted string was opened with \" or ' but the input ended (rune 0) before the matching closing quote. readString (internal/query/lexer.go:201) reports the position where the string started, not where EOF was hit.","triggerScenarios":"query.Parse with an unbalanced quote: `title=\"bug report`, `status='open`, or a quote opened and a newline/EOF reached because the closing quote was deleted during editing.","commonSituations":"Truncated shell commands (closing quote on the same line got eaten by shell quoting rules); hand-edited saved queries; copying text where the trailing quote was lost; embedding values containing quotes without escaping (e.g. `title=\"say \\\"hi\\\"\"` miscounted).","solutions":["Add the matching closing quote at the end of the string value.","If the value itself contains the same quote character, escape it with a backslash: `title=\"say \\\"hi\\\"\"`.","Check shell quoting: a single-quoted shell string can swallow the query's quotes; wrap the whole query in double quotes at the shell level.","Verify the query text programmatically: count unescaped \" and ' characters before calling Parse."],"exampleFix":"// before\nquery.Parse(\"title=\\\"bug report\")\n// after\nquery.Parse(\"title=\\\"bug report\\\"\")","handlingStrategy":"validation","validationCode":"func balancedQuotes(q string) bool {\n    var q1, q2 bool\n    for i := 0; i < len(q); i++ {\n        switch q[i] {\n        case '\\\\':\n            i++\n        case '\\'':\n            q1 = !q1\n        case '\"':\n            q2 = !q2\n        }\n    }\n    return !q1 && !q2\n}","typeGuard":null,"tryCatchPattern":"if _, err := query.Parse(q); err != nil {\n    var pos int\n    if _, scan := fmt.Sscanf(err.Error(), \"unterminated string starting at position %d\", &pos); scan == nil {\n        return fmt.Errorf(\"missing closing quote for string at byte %d\", pos)\n    }\n    return err\n}","preventionTips":["Always close quotes on the same line; the lexer has no multi-line strings","Escape embedded quotes with backslash (\\\" or \\')","When passing through a shell, wrap the whole query in double quotes so inner quotes survive","Run balancedQuotes() on generated queries before Parse"],"tags":["query-language","lexer","unterminated-string"],"backgroundTag":"unterminated-string-literal","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}