gastownhall/beads · error
expected ')' at position %d, got %s
Error message
expected ')' at position %d, got %s
What it means
parsePrimary (internal/query/parser.go:222) parsed the contents of a '(' group via parseOr but the next token was not ')'. It reports the current token's position and type (e.g. EOF, IDENT, AND). Raised through parseNot <- parseAnd <- parseOr whenever a group is left unclosed.
Source
Thrown at internal/query/parser.go:222
}
return &NotNode{Operand: operand}, nil
}
return p.parsePrimary()
}
// parsePrimary parses primary expressions (comparisons and parenthesized expressions).
func (p *Parser) parsePrimary() (Node, error) {
if p.current.Type == TokenLParen {
if err := p.advance(); err != nil {
return nil, err
}
node, err := p.parseOr()
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 areView on GitHub (pinned to 71377f2769)
Solutions
- Add the missing ')' for the group opened at the position reported by the preceding '(' — balance every '(' with a ')'.
- Quote the entire query at the shell level (`bd list "(a=1 OR b=2)"`) so the shell does not consume the parentheses.
- Count parens programmatically before calling Parse (ignoring quoted sections).
- Rebuild the query from smaller pieces, testing each group parses before combining.
Example fix
// before
query.Parse("(status=open OR status=blocked")
// after
query.Parse("(status=open OR status=blocked)") Defensive patterns
Strategy: validation
Validate before calling
// Check parenthesis balance outside quoted strings
func parensBalanced(q string) bool {
depth := 0
var inQuote rune
for i := 0; i < len(q); i++ {
c := q[i]
if inQuote != 0 {
if c == '\\' { i++ } else if rune(c) == inQuote { inQuote = 0 }
continue
}
switch c {
case '\'', '"':
inQuote = rune(c)
case '(':
depth++
case ')':
depth--
}
if depth < 0 { return false }
}
return depth == 0
} Try / catch
if _, err := query.Parse(q); err != nil && strings.Contains(err.Error(), "expected ')'") {
return fmt.Errorf("unclosed group in query; balance your parentheses: %w", err)
} Prevention
- Quote the whole query in the shell: bd list "(a=1 OR b=2)" — unquoted parens are shell syntax
- Count open/close parens before saving a query
- Edit groups as complete units; test each group parses before nesting
- Run parensBalanced() on programmatically generated queries
When it happens
Trigger: query.Parse with unbalanced parentheses: `(status=open OR status=blocked` (missing final ')'), `(status=open AND priority>1 updated>7d` , or a nested group missing its closer: `((a=1 OR b=2) AND c=3`.
Common situations: Hand-edited queries losing a paren during modification; deeply nested filters where count of opens/closes diverges; shell interpretations stripping parens (unquoted `(` and `)` are shell syntax and get eaten before reaching bd — the query then arrives unbalanced).
Related errors
- empty query
- unexpected token %q at position %d (expected end of query)
- expected field name at position %d, got %s
- expected comparison operator at position %d, got %s
- unexpected token after expression
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/1d21cf5104b2ad84.
Report an issue: GitHub.