vitest-dev/vitest · error · Error

Tag name "${tag.name}" is already defined in "test.tags". Ta

Error message

Tag name "${tag.name}" is already defined in "test.tags". Tag names must be unique.

What it means

Thrown during tag validation when two entries in `test.tags` share the same `name`. Vitest tracks tag names in a Set as it iterates; encountering a duplicate means filtering by that name would be ambiguous, so it is rejected. Tag names must be unique across the whole config.

Source

Thrown at packages/vitest/src/node/config/resolveConfig.ts:265

  if ('workspace' in resolved) {
    throw new Error('The `test.workspace` option was removed in Vitest 4. Please, migrate to `test.projects` instead. See https://vitest.dev/guide/projects for examples.')
  }

  resolved.pool ??= 'forks'

  resolved.project = toArray(resolved.project)
  resolved.provide ??= {}

  // shallow copy tags array to avoid mutating user config
  resolved.tags = [...resolved.tags || []]
  const definedTags = new Set<string>()
  resolved.tags.forEach((tag) => {
    if (!tag.name || typeof tag.name !== 'string') {
      throw new Error(`Each tag defined in "test.tags" must have a "name" property, received: ${JSON.stringify(tag)}`)
    }
    if (definedTags.has(tag.name)) {
      throw new Error(`Tag name "${tag.name}" is already defined in "test.tags". Tag names must be unique.`)
    }
    if (/\s/.test(tag.name)) {
      throw new Error(`Tag name "${tag.name}" is invalid. Tag names cannot contain spaces.`)
    }
    if (/[!()*|&]/.test(tag.name)) {
      throw new Error(`Tag name "${tag.name}" is invalid. Tag names cannot contain "!", "*", "&", "|", "(", or ")".`)
    }
    if (/^\s*(?:and|or|not)\s*$/i.test(tag.name)) {
      throw new Error(`Tag name "${tag.name}" is invalid. Tag names cannot be a logical operator like "and", "or", "not".`)
    }
    if (typeof tag.retry === 'object' && typeof tag.retry.condition === 'function') {
      throw new TypeError(`Tag "${tag.name}": retry.condition function cannot be used inside a config file. Use a RegExp pattern instead, or define the function in your test file.`)
    }
    if (tag.priority != null && (typeof tag.priority !== 'number' || tag.priority < 0)) {
      throw new TypeError(`Tag "${tag.name}": priority must be a non-negative number.`)
    }
    definedTags.add(tag.name)
  })

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Deduplicate tags so each name appears once; merge metadata for the colliding tags into a single entry.
  2. When merging presets, dedupe by name in a config-builder function before assigning to `test.tags`.
  3. Namespace tag names if two distinct concepts collided (e.g. 'unit-core' vs 'unit-cli').

Example fix

// before
export default defineConfig({ test: { tags: [
  { name: 'unit', retry: 1 },
  { name: 'unit', priority: 2 },
] } })
// after
export default defineConfig({ test: { tags: [
  { name: 'unit', retry: 1, priority: 2 },
] } })
Defensive patterns

Strategy: validation

Validate before calling

const seen = new Set<string>()
for (const t of tags) {
  if (seen.has(t.name)) throw new Error(`duplicate tag: ${t.name}`)
  seen.add(t.name)
}

Prevention

When it happens

Trigger: Defining `test.tags: [{ name: 'unit' }, { name: 'unit' }]`, or merging multiple config presets that both declare a tag with the same name (deepMerge concatenates/overwrites, leaving duplicates).

Common situations: Combining shared tag presets; copy-paste; monorepo per-project configs merged at the root that each define the same tag.


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