{"record":{"id":"1d21cf5104b2ad84","repo":"gastownhall/beads","slug":"expected-at-position-d-got-s","errorCode":null,"errorMessage":"expected ')' at position %d, got %s","messagePattern":"expected '\\)' at position (.+?), got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/query/parser.go","lineNumber":222,"sourceCode":"\t\t}\n\t\treturn &NotNode{Operand: operand}, nil\n\t}\n\n\treturn p.parsePrimary()\n}\n\n// parsePrimary parses primary expressions (comparisons and parenthesized expressions).\nfunc (p *Parser) parsePrimary() (Node, error) {\n\tif p.current.Type == TokenLParen {\n\t\tif err := p.advance(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnode, err := p.parseOr()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif p.current.Type != TokenRParen {\n\t\t\treturn nil, fmt.Errorf(\"expected ')' at position %d, got %s\", p.current.Pos, p.current.Type.String())\n\t\t}\n\t\tif err := p.advance(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn node, nil\n\t}\n\n\treturn p.parseComparison()\n}\n\n// parseComparison parses a field comparison.\nfunc (p *Parser) parseComparison() (Node, error) {\n\tif p.current.Type != TokenIdent {\n\t\treturn nil, fmt.Errorf(\"expected field name at position %d, got %s\", p.current.Pos, p.current.Type.String())\n\t}\n\n\t// Field names are case-insensitive, but the key suffix of a\n\t// metadata.<key> field keeps its original case: metadata keys are","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/query/parser.go#L204-L240","documentation":"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.","triggerScenarios":"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`.","commonSituations":"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).","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."],"exampleFix":"// before\nquery.Parse(\"(status=open OR status=blocked\")\n// after\nquery.Parse(\"(status=open OR status=blocked)\")","handlingStrategy":"validation","validationCode":"// Check parenthesis balance outside quoted strings\nfunc parensBalanced(q string) bool {\n    depth := 0\n    var inQuote rune\n    for i := 0; i < len(q); i++ {\n        c := q[i]\n        if inQuote != 0 {\n            if c == '\\\\' { i++ } else if rune(c) == inQuote { inQuote = 0 }\n            continue\n        }\n        switch c {\n        case '\\'', '\"':\n            inQuote = rune(c)\n        case '(':\n            depth++\n        case ')':\n            depth--\n        }\n        if depth < 0 { return false }\n    }\n    return depth == 0\n}","typeGuard":null,"tryCatchPattern":"if _, err := query.Parse(q); err != nil && strings.Contains(err.Error(), \"expected ')'\") {\n    return fmt.Errorf(\"unclosed group in query; balance your parentheses: %w\", err)\n}","preventionTips":["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"],"tags":["query-language","parser","unbalanced-parens"],"backgroundTag":"unbalanced-parentheses","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}