vitest-dev/vitest · error · Error
Invalid tags expression: unexpected "${formatToken(stream.pe
Error message
Invalid tags expression: unexpected "${formatToken(stream.peek())}" in "${expr}" What it means
Thrown by parseTagsExpression when a tag filter expression has already been fully parsed but the token stream still has a non-EOF token at the top level. This means the grammar parsed a complete or/and/not expression and then encountered something that cannot legally follow it (e.g. two tags in a row with no operator, or a stray ')'). It signals a malformed --tag / tagsFilter string.
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 d568f8ce37)
Solutions
- Join multiple tags with an explicit operator: use `a && b` or `a and b` instead of `a b`.
- Remove the stray token the error points at (e.g. an extra closing paren or duplicate tag).
- Re-read the supported grammar: TAG, !NOT, &&/and, ||/or, and (...) grouping.
- Test the expression in isolation with the --tag CLI flag to iterate quickly.
Example fix
// before vitest --tag 'unit smoke' // after vitest --tag 'unit && smoke'
Defensive patterns
Strategy: validation
Validate before calling
// Validate a tag expression before passing it to --tag
const TOKEN = /^[!()]+|&&|\|\||\b(?:and|or|not)\b|[^\s!()&|]+/gi
function looksBalanced(expr: string): boolean {
let depth = 0
for (const ch of expr) {
if (ch === '(') depth++
if (ch === ')') depth--
if (depth < 0) return false
}
return depth === 0
}
if (!looksBalanced(process.env.TAG_FILTER!)) throw new Error('bad tag filter') Prevention
- Always join tags with explicit && / || / !.
- Never juxtapose two bare tags.
- Validate parentheses are balanced before running.
When it happens
Trigger: Passing a --tag filter with two bare tags separated only by whitespace, e.g. `--tag 'unit smoke'`. Using a closing parenthesis at the top level like `--tag 'unit)'`. Any string where, after a valid or/and/not expression, an unexpected token remains.
Common situations: Writing `--tag 'a b'` instead of `--tag 'a && b'`. Leftover characters after editing a complex filter expression. Misunderstanding that bare juxtaposition is not an implicit AND (you must use && / and).
Related errors
- Invalid tags expression: missing closing ")" in "${this.expr
- 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/683b68d1e649511f.json.
Report an issue: GitHub.