vitest-dev/vitest · error · Error
Invalid tags expression: missing closing ")" in "${this.expr
Error message
Invalid tags expression: missing closing ")" in "${this.expr}" What it means
Thrown by TokenStream.expect('RPAREN') when it is looking for a closing parenthesis but hits EOF instead. This happens inside parsePrimaryExpression after consuming a '(' and parsing a nested or-expression: the stream expected ')' to close the group but the expression ended first.
Source
Thrown at packages/vitest/src/runtime/runner/utils/tags.ts:182
| { type: 'or'; left: ASTNode; right: ASTNode }
class TokenStream {
private pos = 0
constructor(private tokens: Token[], public expr: string) {}
peek(): Token {
return this.tokens[this.pos]
}
next(): Token {
return this.tokens[this.pos++]
}
expect(type: Token['type']): Token {
const token = this.next()
if (token.type !== type) {
if (type === 'RPAREN' && token.type === 'EOF') {
throw new Error(`Invalid tags expression: missing closing ")" in "${this.expr}"`)
}
throw new Error(`Invalid tags expression: expected "${formatTokenType(type)}" but got "${formatToken(token)}" in "${this.expr}"`)
}
return token
}
unexpectedToken(): never {
const token = this.peek()
if (token.type === 'EOF') {
throw new Error(`Invalid tags expression: unexpected end of expression in "${this.expr}"`)
}
throw new Error(`Invalid tags expression: unexpected "${formatToken(token)}" in "${this.expr}"`)
}
}
function formatTokenType(type: Token['type']): string {
switch (type) {
case 'TAG': return 'tag'View on GitHub (pinned to d568f8ce37)
Solutions
- Add the missing ')' to balance every '('.
- Count parentheses in the expression and ensure they are balanced.
- Simplify the expression into separate --tag flags if nesting gets confusing.
- For one-level grouping, drop the parentheses entirely: `unit && smoke` instead of `(unit && smoke)`.
Example fix
// before vitest --tag '(unit && smoke' // after vitest --tag '(unit && smoke)'
Defensive patterns
Strategy: validation
Validate before calling
function isBalancedParens(expr: string): boolean {
let depth = 0
for (const ch of expr) {
if (ch === '(') depth++
else if (ch === ')') depth--
if (depth < 0) return false
}
return depth === 0
} Prevention
- Count parens in every tag filter expression.
- Prefer flat expressions over nested grouping where possible.
- Add a preflight check that rejects unbalanced strings.
When it happens
Trigger: A tag filter expression with an unbalanced opening parenthesis, e.g. `--tag '(unit && smoke'`. The parser enters parsePrimaryExpression, sees LPAREN, recurses, consumes all tokens, then expect(RPAREN) finds EOF.
Common situations: Manually editing a complex filter and deleting the closing paren. Copy-paste of a partial expression. Complex nested filters where it is easy to lose track of grouping.
Related errors
- Invalid tags expression: unexpected "${formatToken(stream.pe
- Invalid tags expression: expected "${formatTokenType(type)}"
- Invalid tags expression: unexpected end of expression in "${
- Invalid tags expression: unexpected "${formatToken(token)}"
- failed to parse ${options.id}
AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03).
Data as JSON: /data/errors/19df35c33fb4bbe6.json.
Report an issue: GitHub.