{"id":"ccfd85d4a28784bb","repo":"vitest-dev/vitest","slug":"tag-tag-name-retry-condition-function-cannot","errorCode":null,"errorMessage":"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.","messagePattern":"Tag \"(.+?)\": retry\\.condition function cannot be used inside a config file\\. Use a RegExp pattern instead, or define the function in your test file\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/node/config/resolveConfig.ts","lineNumber":277,"sourceCode":"  const definedTags = new Set<string>()\n  resolved.tags.forEach((tag) => {\n    if (!tag.name || typeof tag.name !== 'string') {\n      throw new Error(`Each tag defined in \"test.tags\" must have a \"name\" property, received: ${JSON.stringify(tag)}`)\n    }\n    if (definedTags.has(tag.name)) {\n      throw new Error(`Tag name \"${tag.name}\" is already defined in \"test.tags\". Tag names must be unique.`)\n    }\n    if (/\\s/.test(tag.name)) {\n      throw new Error(`Tag name \"${tag.name}\" is invalid. Tag names cannot contain spaces.`)\n    }\n    if (/[!()*|&]/.test(tag.name)) {\n      throw new Error(`Tag name \"${tag.name}\" is invalid. Tag names cannot contain \"!\", \"*\", \"&\", \"|\", \"(\", or \")\".`)\n    }\n    if (/^\\s*(?:and|or|not)\\s*$/i.test(tag.name)) {\n      throw new Error(`Tag name \"${tag.name}\" is invalid. Tag names cannot be a logical operator like \"and\", \"or\", \"not\".`)\n    }\n    if (typeof tag.retry === 'object' && typeof tag.retry.condition === 'function') {\n      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.`)\n    }\n    if (tag.priority != null && (typeof tag.priority !== 'number' || tag.priority < 0)) {\n      throw new TypeError(`Tag \"${tag.name}\": priority must be a non-negative number.`)\n    }\n    definedTags.add(tag.name)\n  })\n\n  resolved.name = typeof options.name === 'string'\n    ? options.name\n    : (options.name?.label || '')\n\n  resolved.color = typeof options.name !== 'string' ? options.name?.color : undefined\n\n  if (resolved.environment === 'browser') {\n    throw new Error(`Looks like you set \"test.environment\" to \"browser\". To enable Browser Mode, use \"test.browser.enabled\" instead.`)\n  }\n\n  resolved.benchmark = {","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/node/config/resolveConfig.ts#L259-L295","documentation":"Thrown when a tag defined in the config file (test.tags) sets retry.condition to a function. Vitest serializes config objects to send them across worker thread boundaries, and functions cannot survive serialization, so the condition would be silently dropped. The guard enforces that config-file tags only use serializable retry conditions (numbers, objects with RegExp patterns). The function form is permitted inside test files where the closure stays in-process.","triggerScenarios":"In vitest.config.ts, set `tags: [{ name: 'flaky', retry: { count: 2, condition: (result) => result.errors > 0 } }]`. The check at resolveConfig.ts:276 fires because `tag.retry` is an object and `tag.retry.condition` is a function.","commonSituations":"Copying a retry condition from a test file into a shared config; writing a project-wide tag policy with custom logic; upgrading from a version where this was unguarded.","solutions":["Replace the function with a RegExp pattern in the config: `retry: { count: 2, condition: /timeout/i }`.","Move the tag with the function-based condition into the test file where it runs in-process: `test.configureTag('flaky', { retry: { count: 2, condition: fn } })`.","Drop the condition and retry unconditionally via a number: `retry: 2`."],"exampleFix":"// before (vitest.config.ts)\nexport default defineConfig({ test: { tags: [{ name: 'flaky', retry: { count: 2, condition: (r) => r.duration > 1000 } }] } })\n// after\nexport default defineConfig({ test: { tags: [{ name: 'flaky', retry: { count: 2, condition: /timeout/i } }] } })","handlingStrategy":"validation","validationCode":"// before passing config to vitest\nimport type { RawTags } from 'vitest/node'\nfunction assertSerializableTags(tags: RawTags[] | undefined) {\n  for (const t of tags ?? []) {\n    if (t.retry && typeof t.retry === 'object' && typeof (t.retry as any).condition === 'function') {\n      throw new Error(`Tag \"${t.name}\": retry.condition must be a RegExp in config, not a function.`)\n    }\n  }\n}","typeGuard":"function isConfigSafeTag(tag: any): tag is { name: string; retry?: number | { condition?: RegExp; count?: number } } {\n  return !(tag.retry && typeof tag.retry === 'object' && typeof tag.retry.condition === 'function')\n}","tryCatchPattern":null,"preventionTips":["Treat config-file values as serializable: never put functions in test.tags.","Keep function-based retry conditions in test files via test.configureTag.","Add a pre-flight lint rule that rejects `retry: { condition:` arrow functions in config files."],"tags":["config","tags","retry","serialization","worker-threads"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}