{"record":{"id":"38176d3865c80a22","repo":"vercel/turborepo","slug":"found-both-turbo-json-and-turbo-jsonc-in-the-same","errorCode":null,"errorMessage":"Found both turbo.json and turbo.jsonc in the same directory: ${dir}\nPlease use either turbo.json or turbo.jsonc, but not both.","messagePattern":"Found both turbo\\.json and turbo\\.jsonc in the same directory: (.+?)\nPlease use either turbo\\.json or turbo\\.jsonc, but not both\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/turbo-utils/src/get-turbo-configs.ts","lineNumber":196,"sourceCode":"    const configPathsByDir: Record<string, Array<string>> = {};\n\n    // Group config paths by directory\n    for (const configPath of configPaths) {\n      const dir = path.dirname(configPath);\n      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- configPathsByDir[dir] can be undefined\n      if (!configPathsByDir[dir]) {\n        configPathsByDir[dir] = [];\n      }\n      configPathsByDir[dir].push(configPath);\n    }\n\n    // Process each directory\n    for (const [dir, dirConfigPaths] of Object.entries(configPathsByDir)) {\n      // If both turbo.json and turbo.jsonc exist in the same directory, throw an error\n      if (dirConfigPaths.length > 1) {\n        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.`;\n        logger.error(errorMessage);\n        throw new Error(errorMessage);\n      }\n\n      const configPath = dirConfigPaths[0];\n      try {\n        const raw = fs.readFileSync(configPath, \"utf8\");\n\n        const turboJsonContent: SchemaV1 = JSON5.parse(raw);\n        // basic config validation\n        const isRootConfig = path.dirname(configPath) === turboRoot;\n        if (isRootConfig) {\n          // invalid - root config with extends\n          if (\"extends\" in turboJsonContent) {\n            continue;\n          }\n        } else if (!(\"extends\" in turboJsonContent)) {\n          // invalid - workspace config with no extends\n          continue;\n        }","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/vercel/turborepo/blob/9f94a7d215b3881942527dd526afa3fc650869d5/packages/turbo-utils/src/get-turbo-configs.ts#L178-L214","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before: both files present in apps/web\napps/web/turbo.json\napps/web/turbo.jsonc\n\n# after: keep exactly one\ngit rm apps/web/turbo.json","handlingStrategy":"validation","validationCode":"import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\nfunction hasConflictingTurboConfig(dir: string): boolean {\n  return existsSync(path.join(dir, \"turbo.json\")) && existsSync(path.join(dir, \"turbo.jsonc\"));\n}\n\nfor (const dir of workspaceDirs) {\n  if (hasConflictingTurboConfig(dir)) throw new Error(`Both turbo.json and turbo.jsonc in ${dir}`);\n}\nawait getTurboConfigs(turboRoot);","typeGuard":null,"tryCatchPattern":"try {\n  getTurboConfigs(turboRoot);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"Found both turbo.json and turbo.jsonc\")) {\n    // parse the directory from the message, prompt deletion of one file, then retry\n  } else throw e;\n}","preventionTips":["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"],"tags":["config","turborepo","file-conflict","monorepo"],"backgroundTag":"conflicting-config-files","analyzedSha":"9f94a7d215b3881942527dd526afa3fc650869d5","analyzedAt":"2026-08-16T19:47:29.531Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}