vitest-dev/vitest · error · Error
Found a circular "projects" definition: "${file}" -> "${file
Error message
Found a circular "projects" definition: "${file}" -> "${file}". Make sure your configuration is correct. What it means
When flattening container entries (config files that themselves declare 'projects'), Vitest tracks the chain of configFile paths (realpath-resolved). If a config file's realpath already appears in the current chain, resolution is cyclic and would loop forever, so it throws listing the full chain.
Source
Thrown at packages/vitest/src/node/projects/resolveProjects.ts:442
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 1fa9837ec2)
Solutions
- Read the chain in the error to see which configs form the cycle.
- Break the cycle by removing the back-reference in one of the configs.
- Avoid globs that re-include a config file that already includes children.
- Keep container/leaf roles separate: containers declare projects, leaves do not re-include containers.
Example fix
// before - a.workspace.ts includes b.config.ts which includes a.workspace.ts // after - b.config.ts declares only leaf projects, no back-reference
Defensive patterns
Strategy: validation
Validate before calling
// Detect cycles in a projects graph before resolution.
function detectCycle(graph: Map<string, string[]>, start: string) {
const stack: string[] = []
const dfs = (node: string): boolean => {
if (stack.includes(node)) throw new Error(`Cycle: ${[...stack, node].join(' -> ')}`)
stack.push(node)
for (const next of graph.get(node) ?? []) if (dfs(next)) return true
stack.pop()
return false
}
dfs(start)
} Prevention
- Keep container configs and leaf configs in separate roles; containers include, leaves do not re-include.
- Avoid globs that re-match a parent config.
- Review cross-references between workspace files.
When it happens
Trigger: Config file A declares projects including config file B, which (directly or transitively) declares projects including config file A again, forming a cycle in the realpath chain.
Common situations: Workspace files that reference each other, a project glob that re-includes the parent config, or a copy-pasted config that points back to its own container.
Related errors
- Project name "${name}" from "${entryFile}" is not unique. Th
- No projects were found in "${relativeFile}". Make sure your
- vitest/browser can be imported only inside the Browser Mode.
- The `test.workspace` option was removed in Vitest 4. Please,
- Vitest received --browser flag, but no project had a browser
AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11).
Data as JSON: /api/errors/22b5946cff3c6b4a.
Report an issue: GitHub.