{"record":{"id":"56f278570a87a84c","repo":"gastownhall/beads","slug":"expected-comparison-operator-at-position-d-got","errorCode":null,"errorMessage":"expected comparison operator at position %d, got %s","messagePattern":"expected comparison operator at position (.+?), got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/query/parser.go","lineNumber":267,"sourceCode":"\t\treturn nil, err\n\t}\n\n\tvar op ComparisonOp\n\tswitch p.current.Type {\n\tcase TokenEquals:\n\t\top = OpEquals\n\tcase TokenNotEquals:\n\t\top = OpNotEquals\n\tcase TokenLess:\n\t\top = OpLess\n\tcase TokenLessEq:\n\t\top = OpLessEq\n\tcase TokenGreater:\n\t\top = OpGreater\n\tcase TokenGreaterEq:\n\t\top = OpGreaterEq\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"expected comparison operator at position %d, got %s\", p.current.Pos, p.current.Type.String())\n\t}\n\n\tif err := p.advance(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Value can be identifier, string, number, or duration\n\tvar value string\n\tvar valueType TokenType\n\tswitch p.current.Type {\n\tcase TokenIdent:\n\t\tvalue = p.current.Value\n\t\tvalueType = TokenIdent\n\tcase TokenString:\n\t\tvalue = p.current.Value\n\t\tvalueType = TokenString\n\tcase TokenNumber:\n\t\tvalue = p.current.Value","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/query/parser.go#L249-L285","documentation":"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.","triggerScenarios":"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.","commonSituations":"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`.","solutions":["Insert an explicit comparison operator: `status=open` instead of `status open`.","Replace `:` with `=` — colon is not an operator in this grammar (it is legal inside identifiers/label values only).","For existence-style intent, write a boolean comparison: `has_metadata_key=true` or `assignee!=\"\"` style forms.","Remove SQL keywords (IS, IN, LIKE) which are not operators here; use =, !=, <, <=, >, >= only."],"exampleFix":"// before\nquery.Parse(\"status open\")\n// after\nquery.Parse(\"status=open\")","handlingStrategy":"validation","validationCode":"var opRe = regexp.MustCompile(`(!?=|<=?|>=?)`)\nfunc hasOperator(field, rest string) bool {\n    return opRe.MatchString(rest)\n}","typeGuard":null,"tryCatchPattern":"if _, err := query.Parse(q); err != nil && strings.Contains(err.Error(), \"expected comparison operator\") {\n    return fmt.Errorf(\"use =, !=, <, <=, > or >= between field and value: %w\", err)\n}","preventionTips":["Always place one of =, !=, <, <=, >, >= between field and value","Do not use Lucene/SQL syntax (:, IS, IN, LIKE, ~) — only the six operators exist","For existence checks write explicit comparisons like has_metadata_key=true","Lint queries for a bare `field value` pattern before parsing"],"tags":["query-language","parser","missing-operator"],"backgroundTag":"query-syntax-error","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}