# Configuration Gotchas

Common mistakes and how to fix them.

## #1 Root Scripts Not Using `turbo run`

Root `package.json` scripts for turbo tasks MUST use `turbo run`, not direct commands.

```json
// WRONG - bypasses turbo, no parallelization or caching
{
  "scripts": {
    "build": "bun build",
    "dev": "bun dev"
  }
}

// CORRECT - delegates to turbo
{
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev"
  }
}
```

**Why this matters:** Running `bun build` or `npm run build` at root bypasses Turborepo entirely - no parallelization, no caching, no dependency graph awareness.

## #2 Using `&&` to Chain Turbo Tasks

Don't use `&&` to chain tasks that turbo should orchestrate.

```json
// WRONG - changeset:publish chains turbo task with non-turbo command
{
  "scripts": {
    "changeset:publish": "bun build && changeset publish"
  }
}

// CORRECT - use turbo run, let turbo handle dependencies
{
  "scripts": {
    "changeset:publish": "turbo run build && changeset publish"
  }
}
```

If the second command (`changeset publish`) depends on build outputs, the turbo task should run through turbo to get caching and parallelization benefits.

## #3 Overly Broad globalDependencies

`globalDependencies` affects hash for ALL tasks in ALL packages. Be specific.

```json
// WRONG - affects all hashes
{
  "globalDependencies": ["**/.env.*local"]
}

// CORRECT - move to specific tasks that need it
{
  "globalDependencies": [".env"],
  "tasks": {
    "build": {
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "outputs": ["dist/**"]
    }
  }
}
```

**Why this matters:** `**/.env.*local` matches .env files in ALL packages, causing unnecessary cache invalidation. Instead:

- Use `globalDependencies` only for truly global files (root `.env`)
- Use task-level `inputs` for package-specific .env files with `$TURBO_DEFAULT$` to preserve default behavior

## #4 Repetitive Task Configuration

Look for repeated configuration across tasks that can be collapsed.

```json
// WRONG - repetitive env and inputs across tasks
{
  "tasks": {
    "build": {
      "env": ["API_URL", "DATABASE_URL"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"]
    },
    "test": {
      "env": ["API_URL", "DATABASE_URL"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"]
    }
  }
}

// BETTER - use globalEnv and globalDependencies
{
  "globalEnv": ["API_URL", "DATABASE_URL"],
  "globalDependencies": [".env*"],
  "tasks": {
    "build": {},
    "test": {}
  }
}
```

**When to use global vs task-level:**

- `globalEnv` / `globalDependencies` - affects ALL tasks, use for truly shared config
- Task-level `env` / `inputs` - use when only specific tasks need it

## #5 Using `../` to Traverse Out of Package in `inputs`

Don't use relative paths like `../` to reference files outside the package. Use `$TURBO_ROOT$` instead.

```json
// WRONG - traversing out of package
{
  "tasks": {
    "build": {
      "inputs": ["$TURBO_DEFAULT$", "../shared-config.json"]
    }
  }
}

// CORRECT - use $TURBO_ROOT$ for repo root
{
  "tasks": {
    "build": {
      "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared-config.json"]
    }
  }
}
```

## #6 MOST COMMON MISTAKE: Creating Root Tasks

**DO NOT create Root Tasks. ALWAYS create package tasks.**

When you need to create a task (build, lint, test, typecheck, etc.):

1. Add the script to **each relevant package's** `package.json`
2. Register the task in root `turbo.json`
3. Root `package.json` only contains `turbo run <task>`

```json
// WRONG - DO NOT DO THIS
// Root package.json with task logic
{
  "scripts": {
    "build": "cd apps/web && next build && cd ../api && tsc",
    "lint": "eslint apps/ packages/",
    "test": "vitest"
  }
}

// CORRECT - DO THIS
// apps/web/package.json
{ "scripts": { "build": "next build", "lint": "eslint .", "test": "vitest" } }

// apps/api/package.json
{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }

// packages/ui/package.json
{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }

// Root package.json - ONLY delegates
{ "scripts": { "build": "turbo run build", "lint": "turbo run lint", "test": "turbo run test" } }

// turbo.json - register tasks
{
  "tasks": {
    "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
    "lint": {},
    "test": {}
  }
}
```

**Why this matters:**

- Package tasks run in **parallel** across all packages
- Each package's output is cached **individually**
- You can **filter** to specific packages: `turbo run test --filter=web`

Root Tasks (`//#taskname`) defeat all these benefits. Only use them for tasks that truly cannot exist in any package (extremely rare).

## #7 Tasks That Need Parallel Execution + Cache Invalidation

Some tasks can run in parallel (don't need built output from dependencies) but must still invalidate cache when dependency source code changes. Using `dependsOn: ["^taskname"]` forces sequential execution. Using no dependencies breaks cache invalidation.

**Use Transit Nodes for these tasks:**

```json
// WRONG - forces sequential execution (SLOW)
"my-task": {
  "dependsOn": ["^my-task"]
}

// ALSO WRONG - no dependency awareness (INCORRECT CACHING)
"my-task": {}

// CORRECT - use Transit Nodes for parallel + correct caching
{
  "tasks": {
    "transit": { "dependsOn": ["^transit"] },
    "my-task": { "dependsOn": ["transit"] }
  }
}
```

**Why Transit Nodes work:**

- `transit` creates dependency relationships without matching any actual script
- Tasks that depend on `transit` gain dependency awareness
- Since `transit` completes instantly (no script), tasks run in parallel
- Cache correctly invalidates when dependency source code changes

**How to identify tasks that need this pattern:** Look for tasks that read source files from dependencies but don't need their build outputs.

## Missing outputs for File-Producing Tasks

**Before flagging missing `outputs`, check what the task actually produces:**

1. Read the package's script (e.g., `"build": "tsc"`, `"test": "vitest"`)
2. Determine if it writes files to disk or only outputs to stdout
3. Only flag if the task produces files that should be cached

```json
// WRONG - build produces files but they're not cached
"build": {
  "dependsOn": ["^build"]
}

// CORRECT - outputs are cached
"build": {
  "dependsOn": ["^build"],
  "outputs": ["dist/**"]
}
```

No `outputs` key is fine for stdout-only tasks. For file-producing tasks, missing `outputs` means Turbo has nothing to cache.

## Forgetting ^ in dependsOn

```json
// WRONG - looks for "build" in SAME package (infinite loop or missing)
"build": {
  "dependsOn": ["build"]
}

// CORRECT - runs dependencies' build first
"build": {
  "dependsOn": ["^build"]
}
```

The `^` means "in dependency packages", not "in this package".

## Missing persistent on Dev Tasks

```json
// WRONG - dependent tasks hang waiting for dev to "finish"
"dev": {
  "cache": false
}

// CORRECT
"dev": {
  "cache": false,
  "persistent": true
}
```

## Package Config Missing extends

```json
// WRONG - packages/web/turbo.json
{
  "tasks": {
    "build": { "outputs": [".next/**"] }
  }
}

// CORRECT
{
  "extends": ["//"],
  "tasks": {
    "build": { "outputs": [".next/**"] }
  }
}
```

Without `"extends": ["//"]`, Package Configurations are invalid.

## Root Tasks Need Special Syntax

To run a task defined only in root `package.json`:

```bash
# WRONG
turbo run format

# CORRECT
turbo run //#format
```

And in dependsOn:

```json
"build": {
  "dependsOn": ["//#codegen"]  // Root package's codegen
}
```

## Overwriting Default Inputs

```json
// WRONG - only watches test files, ignores source changes
"test": {
  "inputs": ["tests/**"]
}

// CORRECT - extends defaults, adds test files
"test": {
  "inputs": ["$TURBO_DEFAULT$", "tests/**"]
}
```

Without `$TURBO_DEFAULT$`, you replace all default file watching.

## Caching Tasks with Side Effects

```json
// WRONG - deploy might be skipped on cache hit
"deploy": {
  "dependsOn": ["build"]
}

// CORRECT
"deploy": {
  "dependsOn": ["build"],
  "cache": false
}
```

Always disable cache for deploy, publish, or mutation tasks.
