gastownhall/beads · error

unexpected character %q at position %d

Error message

unexpected character %q at position %d

What it means

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.

Source

Thrown at internal/query/lexer.go:191

		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)
		}
		if isIdentStart(r) {
			l.backup()
			return l.readIdent(startPos)
		}
		return Token{}, fmt.Errorf("unexpected character %q at position %d", r, startPos)
	}
}

// readString reads a quoted string.
func (l *Lexer) readString(quote rune, startPos int) (Token, error) {
	var sb strings.Builder
	for {
		r := l.next()
		if r == 0 {
			return Token{}, fmt.Errorf("unterminated string starting at position %d", startPos)
		}
		if r == quote {
			return Token{Type: TokenString, Value: sb.String(), Pos: startPos}, nil
		}
		if r == '\\' {
			// Handle escape sequences
			escaped := l.next()
			switch escaped {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Remove or replace the offending character at the reported position (note: positions are byte offsets).
  2. Replace SQL-style operators: use AND/OR instead of &&/||, and drop trailing ';'.
  3. Quote literal values that contain special characters: `title="a&b"`.
  4. Replace smart quotes/dashes with plain ASCII ('"', '-').

Example fix

// before
query.Parse("status = open;")
// after
query.Parse("status = open")
Defensive patterns

Strategy: validation

Validate before calling

// Reject characters the lexer cannot start a token with
var invalid = regexp.MustCompile(`[;#*@~&|`]`)
func queryHasBadChars(q string) bool {
    inQuote := rune(0)
    for _, r := range q {
        if inQuote != 0 {
            if r == inQuote { inQuote = 0 }
            continue
        }
        if r == '"' || r == '\'' { inQuote = r; continue }
        if invalid.MatchString(string(r)) { return true }
    }
    return false
}

Try / catch

tokens, err := query.NewLexer(q).Tokenize()
if err != nil {
    return fmt.Errorf("invalid query %q: %w", q, err)
}

Prevention

When it happens

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

Common situations: 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.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/b9f7a5247b33429a. Report an issue: GitHub.