vercel/turborepo · error · Error
May not specify workspace name in non-root turbo.json
Error message
May not specify workspace name in non-root turbo.json
What it means
eslint-plugin-turbo iterates every workspace's turbo.json and throws when a non-root (workspace-level) turbo.json defines tasks using the `workspace#task` form. Only the root turbo.json may address other workspaces; workspace-level task keys are implicitly scoped to that workspace, so a workspace prefix is erroneous.
Source
Thrown at packages/eslint-plugin-turbo/lib/utils/calculate-inputs.ts:363
taskDefinition
);
}
}
);
}
for (const projectWorkspace of this.projectWorkspaces) {
if (!projectWorkspace.turboConfig) {
continue;
}
forEachTaskDef(
projectWorkspace.turboConfig,
([taskName, taskDefinition]) => {
const { workspaceName: erroneousWorkspaceName, scriptName } =
getTaskAddress(taskName);
if (erroneousWorkspaceName) {
throw new Error(
"May not specify workspace name in non-root turbo.json"
);
}
const workspaceName = projectWorkspace.workspaceName;
workspaceTasks[workspaceName] =
workspaceName in workspaceTasks
? workspaceTasks[workspaceName]
: {};
workspaceTasks[workspaceName][scriptName] = processTask(
projectWorkspace.workspacePath,
taskDefinition
);
}
);
}
return {View on GitHub (pinned to 9f94a7d215)
Solutions
- In the workspace-level turbo.json, drop the `workspace#` prefix — keys there are implicitly local (`"build"` instead of `"apps/web#build"`)
- If the config genuinely targets another workspace's task, move it into the root turbo.json, which is the only place workspace-qualified keys are allowed
- Re-run ESLint to confirm the plugin resolves the config
Example fix
// apps/web/turbo.json (before)
"tasks": { "apps/web#build": { "outputs": [".next/**"] } }
// after
"tasks": { "build": { "outputs": [".next/**"] } } Defensive patterns
Strategy: validation
Validate before calling
import fs from 'node:fs';
function workspaceConfigUsesScopedKeys(workspaceTurboJsonPath: string): boolean {
const cfg = JSON.parse(fs.readFileSync(workspaceTurboJsonPath, 'utf8'));
const tasks = cfg.tasks ?? cfg.pipeline ?? {};
return Object.keys(tasks).some((k) => k.includes('#')); // illegal outside root turbo.json
} Type guard
function isWorkspaceScopedKey(key: string): boolean {
return key.includes('#');
} Prevention
- Keep workspace-qualified `workspace#task` keys only in the root turbo.json
- When copying root config into a workspace, strip the prefixes first
- Document the convention in the repo: workspace turbo.json keys are implicitly local
When it happens
Trigger: A workspace-level turbo.json (e.g. apps/web/turbo.json) containing a key like `"packages/ui#build"` or `"apps/web#test"` in its tasks/pipeline object. The throw happens while the ESLint rule processes that workspace's turboConfig.
Common situations: Copying the root turbo.json into a workspace as a starting point and forgetting to strip prefixes; assuming workspace configs need self-referencing `my-workspace#build` keys.
Related errors
- Invalid task name found in turbo.json.
- New workspace root detected - unexpected 'workspaces' field
- New workspace is missing a package.json file
- New workspace root detected - unexpected pnpm-workspace.yaml
- No config at "${configPath}"
AI-assisted analysis of vercel/turborepo@9f94a7d215 (2026-08-16).
Data as JSON: /api/errors/aac2d3a698120912.
Report an issue: GitHub.