gastownhall/beads · error

expected comparison operator at position %d, got %s

Error message

expected comparison operator at position %d, got %s

What it means

After reading a field name, parseComparison (internal/query/parser.go:267) expects one of the six comparison operators (=, !=, <, <=, >, >=). Any other token type — an identifier, string, number, EOF, AND/OR/NOT, or paren — produces this error. The common case is a field name immediately followed by a value with no operator between them.

Source

Thrown at internal/query/parser.go:267

		return nil, err
	}

	var op ComparisonOp
	switch p.current.Type {
	case TokenEquals:
		op = OpEquals
	case TokenNotEquals:
		op = OpNotEquals
	case TokenLess:
		op = OpLess
	case TokenLessEq:
		op = OpLessEq
	case TokenGreater:
		op = OpGreater
	case TokenGreaterEq:
		op = OpGreaterEq
	default:
		return nil, fmt.Errorf("expected comparison operator at position %d, got %s", p.current.Pos, p.current.Type.String())
	}

	if err := p.advance(); err != nil {
		return nil, err
	}

	// Value can be identifier, string, number, or duration
	var value string
	var valueType TokenType
	switch p.current.Type {
	case TokenIdent:
		value = p.current.Value
		valueType = TokenIdent
	case TokenString:
		value = p.current.Value
		valueType = TokenString
	case TokenNumber:
		value = p.current.Value

View on GitHub (pinned to 71377f2769)

Solutions

  1. Insert an explicit comparison operator: `status=open` instead of `status open`.
  2. Replace `:` with `=` — colon is not an operator in this grammar (it is legal inside identifiers/label values only).
  3. For existence-style intent, write a boolean comparison: `has_metadata_key=true` or `assignee!=""` style forms.
  4. Remove SQL keywords (IS, IN, LIKE) which are not operators here; use =, !=, <, <=, >, >= only.

Example fix

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

Strategy: validation

Validate before calling

var opRe = regexp.MustCompile(`(!?=|<=?|>=?)`)
func hasOperator(field, rest string) bool {
    return opRe.MatchString(rest)
}

Try / catch

if _, err := query.Parse(q); err != nil && strings.Contains(err.Error(), "expected comparison operator") {
    return fmt.Errorf("use =, !=, <, <=, > or >= between field and value: %w", err)
}

Prevention

When it happens

Trigger: query.Parse with `status open` (space instead of =), `status:"open"` (colon is not an operator), `status~open` (tilde unsupported), `status =` followed by EOF then the error fires on EOF... actually the operator slot receives TokenIdent in `status open`, TokenString in `status "open"`, TokenEOF in `status` alone.

Common situations: Users typing SQL-style `status IS open` or `field:value` syntax (Lucene/GitHub habit); dropping the `=` during editing; bare field names intended as existence checks (`has_metadata_key` alone) — existence-style fields must still be given a comparison, e.g. `has_metadata_key=true`.

Related errors


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