gastownhall/beads · error

expected field name at position %d, got %s

Error message

expected field name at position %d, got %s

What it means

parseComparison (internal/query/parser.go:236) requires the expression to begin with a TokenIdent (the field name). Any other token — a string, number, operator, '(' or EOF — triggers this error naming the token type. Effectively, a comparison cannot start with a literal or operator.

Source

Thrown at internal/query/parser.go:236

		if err != nil {
			return nil, err
		}
		if p.current.Type != TokenRParen {
			return nil, fmt.Errorf("expected ')' at position %d, got %s", p.current.Pos, p.current.Type.String())
		}
		if err := p.advance(); err != nil {
			return nil, err
		}
		return node, nil
	}

	return p.parseComparison()
}

// parseComparison parses a field comparison.
func (p *Parser) parseComparison() (Node, error) {
	if p.current.Type != TokenIdent {
		return nil, fmt.Errorf("expected field name at position %d, got %s", p.current.Pos, p.current.Type.String())
	}

	// Field names are case-insensitive, but the key suffix of a
	// metadata.<key> field keeps its original case: metadata keys are
	// case-sensitive JSON keys (has_metadata_key and the --metadata flag
	// already preserve case), so lowercasing it would make mixed-case keys
	// silently unqueryable.
	field := strings.ToLower(p.current.Value)
	if strings.HasPrefix(field, "metadata.") && len(field) > len("metadata.") {
		field = "metadata." + p.current.Value[len("metadata."):]
	}
	if err := p.advance(); err != nil {
		return nil, err
	}

	var op ComparisonOp
	switch p.current.Type {
	case TokenEquals:

View on GitHub (pinned to 71377f2769)

Solutions

  1. Start the comparison with an unquoted field name (identifier): `status=open`, not `"status"=open`.
  2. Check KnownFields (query.KnownFields) for valid field names; a misspelled leading keyword like `AN status=open` still lexes as ident 'AN' but `AND status=open` fails here if it reaches comparison position.
  3. Ensure the field placeholder in generated queries is non-empty before the operator.
  4. Do not begin an expression with a literal; the language has no literal-to-literal comparisons.

Example fix

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

Strategy: validation

Validate before calling

if query.KnownFields[field] {
    // safe to build "field=value"
}

Try / catch

if _, err := query.Parse(q); err != nil && strings.Contains(err.Error(), "expected field name") {
    return fmt.Errorf("comparison must start with an unquoted field name: %w", err)
}

Prevention

When it happens

Trigger: query.Parse where a value appears where a field is expected: `"status"=open` (quoted field name), `=open`, `7d>created`, `AND status=open`, `(status=open` reaching parseComparison without a field (e.g. `NOT ( )` -> group then comparison on ')'), or a number leading the query like `1=1`.

Common situations: Quoting field names out of habit from other query languages; strings beginning with an operator because the field name was typo'd into a keyword (e.g. starting with AND/OR/NOT followed by nothing comparable); template output where the field placeholder was empty and only the value remained.

Related errors


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