vitest-dev/vitest · error · Error

Found a circular "projects" definition: ${[...chain, realCon

Error message

Found a circular "projects" definition: ${[...chain, realConfigFile].map(file => `"${relative(context.rootConfig.root, file)}"`).join(' -> ')}. Make sure your configuration is correct.

What it means

flattenContainerEntries (resolveProjects.ts:426-436) walks each container config (a file-based config that itself declares projects). It maintains a chain of realpath'd config files; if a container's file is already in the chain, the projects definitions form a cycle and Vitest throws, printing the cycle as file -> file -> file.

Source

Thrown at packages/vitest/src/node/projects/resolveProjects.ts:429

  for (const entry of entries) {
    const definitions = entry.projectConfig.projects
    // inline projects cannot declare `projects`; the declaring config's own
    // entry (emitted when it references its own config file) is kept as-is —
    // its `projects` are the definitions currently being resolved
    if (entry.inline || definitions === undefined || entry.projectConfig === context.parentConfig) {
      result.push(entry)
      continue
    }

    const configFile = entry.viteConfig.configFile
    const relativeFile = configFile
      ? relative(context.rootConfig.root, configFile)
      : entry.projectConfig.name
    let chain = context.chain
    if (configFile) {
      const realConfigFile = safeRealpath(configFile)
      if (chain.includes(realConfigFile)) {
        throw new Error(
          [
            `Found a circular "projects" definition: `,
            [...chain, realConfigFile].map(file => `"${relative(context.rootConfig.root, file)}"`).join(' -> '),
            '. Make sure your configuration is correct.',
          ].join(''),
        )
      }
      chain = [...chain, realConfigFile]
      context.containerConfigFiles.push(configFile)
    }

    const childContext: ProjectsResolutionContext = {
      ...context,
      parentViteConfig: entry.viteConfig,
      parentConfig: entry.projectConfig,
      ancestors: [...context.ancestors, entry.projectConfig.name],
      chain,
    }

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Inspect the printed cycle and break it by removing the back-reference.
  2. Use `extends` (single-parent inheritance) instead of mutual `projects` references for shared config.
  3. Centralize shared config in a base file that both projects extend, rather than referencing each other.
  4. If a config should also run as a project, list it once from the root and don't re-list it from a child.

Example fix

// before: a.config.ts projects: ['./b.config.ts']; b.config.ts projects: ['./a.config.ts']

// after: shared base, both extend it
// base.config.ts exports common test config
// a.config.ts: export default defineConfig([{ test: { extends: './base.config.ts', ... } }])
Defensive patterns

Strategy: validation

Validate before calling

function detectCycle(adjacency: Map<string, string[]>, start: string): string[] | null {
  const seen = new Set<string>()
  const stack: string[] = []
  function visit(node: string): string[] | null {
    if (stack.includes(node)) return [...stack.slice(stack.indexOf(node)), node]
    if (seen.has(node)) return null
    seen.add(node); stack.push(node)
    for (const next of adjacency.get(node) ?? []) if (visit(next)) return visit(next)
    stack.pop(); return null
  }
  return visit(start)
}

Type guard

function hasCycle(files: string[], refs: Map<string, string[]>): boolean {
  return detectCycle(refs, files[0]) !== null
}

Prevention

When it happens

Trigger: Config A's projects array references config B, and config B's projects array references config A; a config lists itself in its own projects; a chain A -> B -> C -> A.

Common situations: Refactoring workspace files into projects arrays and accidentally creating a back-reference; two configs meant to extend each other via `extends` but wired through `projects` instead; copy-paste of a projects array that includes the source file.

Related errors


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