{"record":{"id":"fbe3bf6ad3f99bcb","repo":"gastownhall/beads","slug":"unexpected-character-at-position-d-did-you-m","errorCode":null,"errorMessage":"unexpected character '!' at position %d (did you mean '!=' or 'NOT'?)","messagePattern":"unexpected character '!' at position (.+?) \\(did you mean '!=' or 'NOT'\\?\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/query/lexer.go","lineNumber":167,"sourceCode":"\tif r == 0 {\n\t\treturn Token{Type: TokenEOF, Pos: startPos}, nil\n\t}\n\n\tswitch r {\n\tcase '(':\n\t\treturn Token{Type: TokenLParen, Value: \"(\", Pos: startPos}, nil\n\tcase ')':\n\t\treturn Token{Type: TokenRParen, Value: \")\", Pos: startPos}, nil\n\tcase ',':\n\t\treturn Token{Type: TokenComma, Value: \",\", Pos: startPos}, nil\n\tcase '=':\n\t\treturn Token{Type: TokenEquals, Value: \"=\", Pos: startPos}, nil\n\tcase '!':\n\t\tif l.peek() == '=' {\n\t\t\tl.next()\n\t\t\treturn Token{Type: TokenNotEquals, Value: \"!=\", Pos: startPos}, nil\n\t\t}\n\t\treturn Token{}, fmt.Errorf(\"unexpected character '!' at position %d (did you mean '!=' or 'NOT'?)\", startPos)\n\tcase '<':\n\t\tif l.peek() == '=' {\n\t\t\tl.next()\n\t\t\treturn Token{Type: TokenLessEq, Value: \"<=\", Pos: startPos}, nil\n\t\t}\n\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)","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/query/lexer.go#L149-L185","documentation":"The lexer encountered a bare '!' that is not immediately followed by '='. The query language only supports '!=' for inequality (or the NOT keyword for logical negation), so a lone '!' is rejected at tokenize time with a hint pointing at both alternatives. This is raised by Lexer.NextToken (internal/query/lexer.go:167) and propagates through Tokenize/Parse.","triggerScenarios":"Calling query.Parse/NewLexer on a string where '!' appears without a following '=', e.g. `status!open` (missing '='), `!!status=open`, `status = open!`, or a shell-history-style `!` character embedded in an unquoted query string.","commonSituations":"Users familiar with shell or other DSLs typing `!=` but dropping the '=' (`status!open`); copy-pasting shell commands where `!` triggers history expansion and mangles the query; trying to negate a comparison with `!status=open` instead of `NOT status=open`.","solutions":["Add the '=' to make a != comparison: `status!=open`.","Use the NOT keyword for logical negation: `NOT status=open`.","If the '!' is literal data (e.g. in a value), quote it: `title=\"hello!\"`.","Check shell history expansion (bash `!` substitution) that may have mangled the query before it reached bd."],"exampleFix":"// before\nquery.Parse(\"status!open\")\n// after\nquery.Parse(\"status!=open\")\n// or\nquery.Parse(\"NOT status=open\")","handlingStrategy":"validation","validationCode":"func validBangUsage(q string) error {\n    for i := 0; i < len(q); i++ {\n        if q[i] == '!' && (i+1 >= len(q) || q[i+1] != '=') {\n            return fmt.Errorf(\"bare '!' at %d; use '!=' or NOT\", i)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"node, err := query.Parse(q)\nif err != nil && strings.Contains(err.Error(), \"unexpected character '!'\") {\n    // surface hint: did you mean '!=' or NOT?\n}","preventionTips":["Always write != as two characters, never ! alone","Use NOT for logical negation of comparisons","Quote values containing punctuation: title=\"done!\"","In bash, disable history expansion (set +H) when queries contain !"],"tags":["query-language","lexer","syntax-error"],"backgroundTag":"query-syntax-error","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}