vitest-dev/vitest · error · Error

Invalid tags expression: expected

Error message

Invalid tags expression: expected "${formatTokenType(type)}" but got "${formatToken(token)}" in "${this.expr}"

What it means

Thrown by TokenStream.expect when a specific token type was expected (the only current call site expects RPAREN after a grouped sub-expression) but a different token was found, and it is not the EOF-after-expecting-paren case covered by error 343. The message names both the expected and actual tokens to pinpoint the mismatch.

Solutions

  1. Inside parentheses put exactly one sub-expression (tag, !tag, or a binary op chain): `--tags '(foo && bar)'`.
  2. Remove the unexpected token identified by 'but got' in the message.
  3. Re-read the grammar: a group is `( <or-expression> )`, so combine tags inside it with && / || / ! only.

Example fix

# before
vitest --tags '(smoke auth)'

# after
vitest --tags '(smoke && auth)'
Defensive patterns

Strategy: validation

Validate before calling

// Inside a group, allow only: TAG, !TAG, ( ... ), joined by && / ||
function validateGroup(group: string): boolean {
  const inner = group.replace(/^[^(]*\(/, '').replace(/\)[^)]*$/, '')
  return /^!?[\w*-]+(?:\s*(?:&&|\|\|)\s*!?[\w*-]+)*$/.test(inner.trim())
}

Prevention

When it happens

Trigger: A `--tags` expression where a grouping opens with `(` but the next significant token is not a valid close, e.g. `vitest --tags '(foo bar)'` (two tags inside parens with no operator — expect RPAREN after 'foo', got TAG 'bar'), or `vitest --tags '(foo &&)'` (expect RPAREN, got an unexpected token).

Common situations: Putting multiple tags inside one pair of parentheses without an operator; trailing operators inside a group; stray punctuation inside a group.

Related errors


AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11). Data as JSON: /api/errors/cc39c0d2aee95435. Report an issue: GitHub.

Appendix: source

Thrown at packages/vitest/src/runtime/runner/utils/tags.ts:184

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'
    case 'AND': return 'and'
    case 'OR': return 'or'

View on GitHub (pinned to 1fa9837ec2)