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

  1. Add the missing ')' to balance every '('.
  2. Count parentheses in the expression and ensure they are balanced.
  3. Simplify the expression into separate --tag flags if nesting gets confusing.
  4. 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

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


AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03). Data as JSON: /data/errors/19df35c33fb4bbe6.json. Report an issue: GitHub.