gastownhall/beads · error
unexpected character '!' at position %d (did you mean '!=' o
Error message
unexpected character '!' at position %d (did you mean '!=' or 'NOT'?)
What it means
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.
Source
Thrown at internal/query/lexer.go:167
if r == 0 {
return Token{Type: TokenEOF, Pos: startPos}, nil
}
switch r {
case '(':
return Token{Type: TokenLParen, Value: "(", Pos: startPos}, nil
case ')':
return Token{Type: TokenRParen, Value: ")", Pos: startPos}, nil
case ',':
return Token{Type: TokenComma, Value: ",", Pos: startPos}, nil
case '=':
return Token{Type: TokenEquals, Value: "=", Pos: startPos}, nil
case '!':
if l.peek() == '=' {
l.next()
return Token{Type: TokenNotEquals, Value: "!=", Pos: startPos}, nil
}
return Token{}, fmt.Errorf("unexpected character '!' at position %d (did you mean '!=' or 'NOT'?)", startPos)
case '<':
if l.peek() == '=' {
l.next()
return Token{Type: TokenLessEq, Value: "<=", Pos: startPos}, nil
}
return Token{Type: TokenLess, Value: "<", Pos: startPos}, nil
case '>':
if l.peek() == '=' {
l.next()
return Token{Type: TokenGreaterEq, Value: ">=", Pos: startPos}, nil
}
return Token{Type: TokenGreater, Value: ">", Pos: startPos}, nil
case '"', '\'':
return l.readString(r, startPos)
default:
if unicode.IsDigit(r) || r == '-' || r == '+' {
l.backup()
return l.readNumberOrDuration(startPos)View on GitHub (pinned to 71377f2769)
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.
Example fix
// before
query.Parse("status!open")
// after
query.Parse("status!=open")
// or
query.Parse("NOT status=open") Defensive patterns
Strategy: validation
Validate before calling
func validBangUsage(q string) error {
for i := 0; i < len(q); i++ {
if q[i] == '!' && (i+1 >= len(q) || q[i+1] != '=') {
return fmt.Errorf("bare '!' at %d; use '!=' or NOT", i)
}
}
return nil
} Try / catch
node, err := query.Parse(q)
if err != nil && strings.Contains(err.Error(), "unexpected character '!'") {
// surface hint: did you mean '!=' or NOT?
} Prevention
- 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 !
When it happens
Trigger: 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.
Common situations: 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`.
Related errors
- unexpected character %q at position %d
- unterminated string starting at position %d
- unterminated escape sequence at position %d
- expected digit at position %d
- gate: invalid condition %q: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/fbe3bf6ad3f99bcb.
Report an issue: GitHub.