vitest-dev/vitest · error · Error

Invalid tags expression: unexpected

Error message

Invalid tags expression: unexpected "${formatToken(stream.peek())}" in "${expr}"

What it means

Thrown by parseTagsExpression after a top-level OR-expression parse completes but the token stream still has a non-EOF token. This indicates the expression parsed successfully up to a point and then an unexpected token followed that cannot extend a valid expression (e.g. two consecutive tags with no operator). It governs the `--tags` CLI filter grammar, which supports tag names plus `&&`/`||`/`!`/parentheses and the words and/or/not.

Solutions

  1. Join multiple required tags with `&&` or the word `and`: `--tags 'foo && bar'`.
  2. Join alternatives with `||` or `or`: `--tags 'foo || bar'`.
  3. Remove the stray token the error points at (the unexpected token is quoted in the message).
  4. Wrap sub-expressions in parentheses only as complete units: `--tags '(foo || bar) && baz'`.

Example fix

# before
vitest --tags 'smoke auth'

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

Strategy: validation

Validate before calling

// Validate a --tags expression before passing to vitest
import { execFileSync } from 'node:child_process'
// Cheap pre-check: every pair of tags must be separated by an operator
function looksBalanced(expr: string): boolean {
  const ops = expr.split(/\s*(?:&&|\|\||\band\b|\bor\b)\s*/i)
  return ops.every(p => p.trim().length > 0)
}

Try / catch

// In a wrapper script that invokes vitest --tags
try {
  runVitest(['--tags', expr])
} catch (e) {
  if (String(e.message).startsWith('Invalid tags expression')) {
    console.error('Tags filter syntax error. Use tag && tag, tag || tag, !tag, ( ... ).')
    process.exit(2)
  }
  throw e
}

Prevention

When it happens

Trigger: Running `vitest --tags 'foo bar'` (two tags with no operator between them), `vitest --tags 'foo )'` (stray closing paren after a tag), or any expression where a second complete sub-expression follows the first without `&&`/`||`/`and`/`or`.

Common situations: Users assume multiple tags are space-separated (AND) like some other tools; misplaced parentheses; pasting a filter expression from documentation that used a different syntax.

Related errors


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

Appendix: source

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

    .map(t => `- ${t.name}${t.description ? `: ${t.description}` : ''}`)
    .join('\n')}`)
}

export function createTagsFilter(tagsExpr: string[], availableTags: TestTagDefinition[]): (testTags: string[]) => boolean {
  const matchers = tagsExpr.map(expr => parseTagsExpression(expr, availableTags))
  return (testTags: string[]) => {
    return matchers.every(matcher => matcher(testTags))
  }
}

type TagMatcher = (tags: string[]) => boolean

function parseTagsExpression(expr: string, availableTags: TestTagDefinition[]): TagMatcher {
  const tokens = tokenize(expr)
  const stream = new TokenStream(tokens, expr)
  const ast = parseOrExpression(stream, availableTags)
  if (stream.peek().type !== 'EOF') {
    throw new Error(`Invalid tags expression: unexpected "${formatToken(stream.peek())}" in "${expr}"`)
  }
  return (tags: string[]) => evaluateNode(ast, tags)
}

function formatToken(token: Token): string {
  switch (token.type) {
    case 'TAG': return token.value
    default: return formatTokenType(token.type)
  }
}

type Token
  = | { type: 'TAG'; value: string }
    | { type: 'AND' }
    | { type: 'OR' }
    | { type: 'NOT' }
    | { type: 'LPAREN' }
    | { type: 'RPAREN' }

View on GitHub (pinned to 1fa9837ec2)