{"record":{"id":"b6a77107fbad1a90","repo":"gastownhall/beads","slug":"unexpected-token-q-at-position-d-expected-end-o","errorCode":null,"errorMessage":"unexpected token %q at position %d (expected end of query)","messagePattern":"unexpected token %q at position (.+?) \\(expected end of query\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/query/parser.go","lineNumber":119,"sourceCode":"}\n\n// Parse parses the query string and returns the root AST node.\nfunc (p *Parser) Parse() (Node, error) {\n\tif err := p.advance(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif p.current.Type == TokenEOF {\n\t\treturn nil, fmt.Errorf(\"empty query\")\n\t}\n\n\tnode, err := p.parseOr()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif p.current.Type != TokenEOF {\n\t\treturn nil, fmt.Errorf(\"unexpected token %q at position %d (expected end of query)\", p.current.Value, p.current.Pos)\n\t}\n\n\treturn node, nil\n}\n\n// advance moves to the next token.\nfunc (p *Parser) advance() error {\n\tif p.peeked != nil {\n\t\tp.current = *p.peeked\n\t\tp.peeked = nil\n\t\treturn nil\n\t}\n\ttok, err := p.lexer.NextToken()\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.current = tok\n\treturn nil","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/query/parser.go#L101-L137","documentation":"After successfully parsing an expression, Parse (internal/query/parser.go:119) checks that the next token is EOF. A leftover token means trailing content that the grammar cannot attach to any node — a complete expression was parsed, but the string continues with junk. The message echoes the offending token value, position, and the expectation.","triggerScenarios":"query.Parse with trailing junk after a valid expression: `status=open status != closed` (two comparisons with no AND/OR), `status=open)` (stray closing paren), `status=open , priority>1` (comma outside a list context), or `NOT` alone followed by nothing then more content that terminates an expression early.","commonSituations":"Users writing two conditions space-separated without AND (`priority<2 status=open`) as in other search DSLs; an unbalanced `)` that terminates the group early leaving leftovers; stray commas copied from list-syntax examples.","solutions":["Insert an explicit boolean operator between conditions: `status=open AND priority>1`.","Remove the stray trailing token (often an extra ')' or ',').","Balance parentheses so the whole expression is consumed before EOF.","Wrap multi-word values in quotes so they lex as one TokenString instead of several tokens: `title=\"bug report\"`."],"exampleFix":"// before\nquery.Parse(\"status=open priority>1\")\n// after\nquery.Parse(\"status=open AND priority>1\")","handlingStrategy":"validation","validationCode":"// Join adjacent comparisons with AND before parsing (simple heuristic)\nfunc joinConditions(q string) string {\n    return regexp.MustCompile(`(\\)|\\w|\")\\s+(?:(?i)(and|or|not)\\b)@!`).ReplaceAllString(q, \"$1 \") // validate manually instead\n}","typeGuard":null,"tryCatchPattern":"node, err := query.Parse(q)\nif err != nil && strings.Contains(err.Error(), \"expected end of query\") {\n    return fmt.Errorf(\"trailing content after a complete expression; join conditions with AND/OR: %w\", err)\n}","preventionTips":["Always write explicit AND/OR between every pair of conditions","Wrap the whole query in quotes at the shell level so nothing is dropped or split","Remove stray ')' or ',' characters after editing saved queries","Quote multi-word values so they form a single token"],"tags":["query-language","parser","trailing-tokens"],"backgroundTag":"query-syntax-error","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}