vercel/turborepo · error · Error
Found both turbo.json and turbo.jsonc in the same directory:
Error message
Found both turbo.json and turbo.jsonc in the same directory: ${dir}
Please use either turbo.json or turbo.jsonc, but not both. What it means
getTurboConfigs() globs the monorepo for turbo.json/turbo.jsonc, groups matches per directory, and throws when a single directory contains more than one config file. Turborepo cannot decide which file wins, so it refuses rather than guessing, and the message names the offending directory.
Source
Thrown at packages/turbo-utils/src/get-turbo-configs.ts:196
const configPathsByDir: Record<string, Array<string>> = {};
// Group config paths by directory
for (const configPath of configPaths) {
const dir = path.dirname(configPath);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- configPathsByDir[dir] can be undefined
if (!configPathsByDir[dir]) {
configPathsByDir[dir] = [];
}
configPathsByDir[dir].push(configPath);
}
// Process each directory
for (const [dir, dirConfigPaths] of Object.entries(configPathsByDir)) {
// If both turbo.json and turbo.jsonc exist in the same directory, throw an error
if (dirConfigPaths.length > 1) {
const errorMessage = `Found both turbo.json and turbo.jsonc in the same directory: ${dir}\nPlease use either turbo.json or turbo.jsonc, but not both.`;
logger.error(errorMessage);
throw new Error(errorMessage);
}
const configPath = dirConfigPaths[0];
try {
const raw = fs.readFileSync(configPath, "utf8");
const turboJsonContent: SchemaV1 = JSON5.parse(raw);
// basic config validation
const isRootConfig = path.dirname(configPath) === turboRoot;
if (isRootConfig) {
// invalid - root config with extends
if ("extends" in turboJsonContent) {
continue;
}
} else if (!("extends" in turboJsonContent)) {
// invalid - workspace config with no extends
continue;
}View on GitHub (pinned to 9f94a7d215)
Solutions
- In the directory named in the message, delete (git rm) either turbo.json or turbo.jsonc so exactly one remains
- If both contain changes, merge their contents into the surviving file first
- Sweep for other duplicates: find . -name 'turbo.json*' -exec dirname {} \; | sort | uniq -d
Example fix
# before: both files present in apps/web apps/web/turbo.json apps/web/turbo.jsonc # after: keep exactly one git rm apps/web/turbo.json
Defensive patterns
Strategy: validation
Validate before calling
import { existsSync } from "node:fs";
import path from "node:path";
function hasConflictingTurboConfig(dir: string): boolean {
return existsSync(path.join(dir, "turbo.json")) && existsSync(path.join(dir, "turbo.jsonc"));
}
for (const dir of workspaceDirs) {
if (hasConflictingTurboConfig(dir)) throw new Error(`Both turbo.json and turbo.jsonc in ${dir}`);
}
await getTurboConfigs(turboRoot); Try / catch
try {
getTurboConfigs(turboRoot);
} catch (e) {
if (e instanceof Error && e.message.includes("Found both turbo.json and turbo.jsonc")) {
// parse the directory from the message, prompt deletion of one file, then retry
} else throw e;
} Prevention
- Delete the old turbo.json in the same commit that introduces turbo.jsonc
- Add a CI check (grep/find) rejecting directories containing both config files
- Watch for merge conflicts that resurrect a removed config file
When it happens
Trigger: Any workspace directory (including the root) containing both turbo.json and turbo.jsonc while getTurboConfigs()/getTurboRoot() runs — e.g. after adding the .jsonc variant without deleting the original, or a merge resurrecting one file.
Common situations: Half-finished migration from turbo.json to turbo.jsonc; merge conflicts reintroducing the deleted file; codegen writing turbo.json into a workspace someone hand-migrated to .jsonc.
Related errors
- Found both turbo.json and turbo.jsonc in the same directory:
- Invalid task name found in turbo.json.
- May not specify workspace name in non-root turbo.json
- New workspace root detected - unexpected 'workspaces' field
- New workspace root detected - unexpected pnpm-workspace.yaml
AI-assisted analysis of vercel/turborepo@9f94a7d215 (2026-08-16).
Data as JSON: /api/errors/38176d3865c80a22.
Report an issue: GitHub.