{"record":{"id":"b9f7a5247b33429a","repo":"gastownhall/beads","slug":"unexpected-character-q-at-position-d","errorCode":null,"errorMessage":"unexpected character %q at position %d","messagePattern":"unexpected character %q at position (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/query/lexer.go","lineNumber":191,"sourceCode":"\t\treturn Token{Type: TokenLess, Value: \"<\", Pos: startPos}, nil\n\tcase '>':\n\t\tif l.peek() == '=' {\n\t\t\tl.next()\n\t\t\treturn Token{Type: TokenGreaterEq, Value: \">=\", Pos: startPos}, nil\n\t\t}\n\t\treturn Token{Type: TokenGreater, Value: \">\", Pos: startPos}, nil\n\tcase '\"', '\\'':\n\t\treturn l.readString(r, startPos)\n\tdefault:\n\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 {","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/query/lexer.go#L173-L209","documentation":"The lexer read a rune that cannot start any token: it is not an operator, quote, digit/sign, or identifier start (letter or underscore). NextToken (internal/query/lexer.go:191) rejects it with the character in %q form and its byte position. Note the lexer advances by single bytes (rune(l.input[l.pos])), so the offending rune is the first byte of any unsupported character.","triggerScenarios":"Any query.NextToken/Tokenize/Parse call where the input contains an unsupported character, e.g. `status = open;` (semicolon), `status=\"open\" AND priority>1 #comment`, `*`, `@`, `~`, unquoted `a&b`, or stray control/non-ASCII punctuation bytes.","commonSituations":"Pasting queries from SQL/JS habit (trailing `;`, `&&`, `||`); comments typed into saved queries; smart quotes or curly dashes from word processors; shell metacharacters (`&`, `|`, `*`) surviving into the query string.","solutions":["Remove or replace the offending character at the reported position (note: positions are byte offsets).","Replace SQL-style operators: use AND/OR instead of &&/||, and drop trailing ';'.","Quote literal values that contain special characters: `title=\"a&b\"`.","Replace smart quotes/dashes with plain ASCII ('\"', '-')."],"exampleFix":"// before\nquery.Parse(\"status = open;\")\n// after\nquery.Parse(\"status = open\")","handlingStrategy":"validation","validationCode":"// Reject characters the lexer cannot start a token with\nvar invalid = regexp.MustCompile(`[;#*@~&|`]`)\nfunc queryHasBadChars(q string) bool {\n    inQuote := rune(0)\n    for _, r := range q {\n        if inQuote != 0 {\n            if r == inQuote { inQuote = 0 }\n            continue\n        }\n        if r == '\"' || r == '\\'' { inQuote = r; continue }\n        if invalid.MatchString(string(r)) { return true }\n    }\n    return false\n}","typeGuard":null,"tryCatchPattern":"tokens, err := query.NewLexer(q).Tokenize()\nif err != nil {\n    return fmt.Errorf(\"invalid query %q: %w\", q, err)\n}","preventionTips":["Drop SQL habits: no trailing semicolons, no &&/|| (use AND/OR)","Quote any value containing special characters","Use plain ASCII quotes and dashes; avoid smart punctuation from editors","Sanitize queries built from user input before parsing"],"tags":["query-language","lexer","invalid-character"],"backgroundTag":"query-syntax-error","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}