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

  1. In the workspace-level turbo.json, drop the `workspace#` prefix — keys there are implicitly local (`"build"` instead of `"apps/web#build"`)
  2. 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
  3. 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

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


AI-assisted analysis of vercel/turborepo@9f94a7d215 (2026-08-16). Data as JSON: /api/errors/aac2d3a698120912. Report an issue: GitHub.