vitest-dev/vitest · error · Error
Projects definition references a non-existing file or a…
Error message
Projects definition references a non-existing file or a directory: ${file} What it means
When `projects` contains a literal string (not a glob), Vitest resolves it relative to the parent config's `root` and verifies the path exists immediately. A missing path is treated as a configuration error rather than silently skipped, so a typo doesn't quietly drop a project.
Solutions
- Correct the path so it points to an existing file or directory.
- If the path is intentionally optional, use a glob (e.g. `./configs/vitest.*.ts`) which is resolved lazily and tolerates zero matches.
- Verify `root` is what you expect (path is resolved against `parentConfig.root`).
Example fix
// before
export default defineConfig({ test: { projects: ['./packages/ap/vitest.conf.ts'] } })
// after (fixed typo)
export default defineConfig({ test: { projects: ['./packages/app/vitest.config.ts'] } })
// after (optional, glob-based)
export default defineConfig({ test: { projects: ['./packages/*/vitest.config.ts'] } }) Defensive patterns
Strategy: validation
Validate before calling
import { existsSync } from 'node:fs'
import { resolve, isAbsolute } from 'node:path'
import pm from 'picomatch'
function assertProjectsPathsExist(root: string, projects: unknown[]) {
for (const def of projects) {
if (typeof def !== 'string') continue
if (pm.isMatch(def, '**') || /[*/?]/.test(def)) continue // glob, resolved lazily
const file = resolve(root, def.replace('<rootDir>', root))
if (!existsSync(file)) {
throw new Error(`projects entry '${def}' does not exist: ${file}`)
}
}
} Prevention
- Use globs for optional project paths so a missing match is tolerated.
- Pin literal paths only to files guaranteed present (e.g. committed sub-configs).
When it happens
Trigger: A `projects` array entry that is a plain string (not a dynamic/glob pattern per `isDynamicPattern`) whose `resolve(root, entry)` fails `existsSync`.
Common situations: Typo in the path; referencing a config file that was renamed or moved; sparse CI checkout missing a sub-project; case-sensitivity mismatch between macOS and Linux.
Related errors
- Access denied to " ". See Vite config documentation for…
- Failed to initialize projects. There were errors during…
- Found a circular "projects" definition
- No projects were found in
- No projects were found. Make sure your configuration is…
AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11).
Data as JSON: /api/errors/0daa5ae350a435ba.
Report an issue: GitHub.
Appendix: source
Thrown at packages/vitest/src/node/projects/resolveProjects.ts:1182
// custom config files that were specified directly or resolved from a directory
const projectsConfigFiles: string[] = []
// custom glob matches that should be resolved as directories or config files
const projectsGlobMatches: string[] = []
// directories that don't have a config file inside, but should be treated as projects
const nonConfigProjectDirectories: string[] = []
for (const definition of projectsDefinition) {
if (typeof definition === 'string') {
const stringOption = definition.replace('<rootDir>', parentConfig.root)
// if the string doesn't contain a glob, we can resolve it directly
// ['./vitest.config.js']
if (!isDynamicPattern(stringOption)) {
const file = resolve(parentConfig.root, stringOption)
if (!existsSync(file)) {
throw new Error(`Projects definition references a non-existing file or a directory: ${file}`)
}
const stats = statSync(file)
// user can specify a config file directly
if (stats.isFile()) {
const name = basename(file)
if (!CONFIG_REGEXP.test(name)) {
throw new Error(
`The file "${relative(parentConfig.root, file)}" must start with "vitest.config"/"vite.config" `
+ `or match the pattern "(vitest|vite).*.config.*" to be a valid project config.`,
)
}
projectsConfigFiles.push(file)
}
// user can specify a directory that should be used as a project
else if (stats.isDirectory()) {
const configFile = resolveDirectoryConfig(file)View on GitHub (pinned to 1fa9837ec2)