Turborepo `watch` not triggering run on root 'inputs' change

I have a lint task set up in my root turbo.json:

"tasks: {
  "lint": {
      "outputs": [],
      "inputs": [
        "$TURBO_DEFAULT$", 
        "$TURBO_ROOT$/eslint.config.mjs"
      ]
    },
    ...
}

I have several packages with a lint task calling eslint in their package.json.

My problem is that turbo watch lint doesn’t rerun when there are changes to eslint.config.mjs.

The caching itself is working. That is, turbo run lint will correctly cache miss if there’s a change in eslint.config.mjs.

If I put eslint.config.mjs into “globalDependencies”, the watcher properly detects a change in the file and reruns the task.

Current behavior:
Change to root-level input file eslint.config.mjs doesn’t cause turbo watch lint to rerun.

Expected behavior:
Change to a root-level input file should cause the watcher to rerun the task.

I’m on MacOS 15.4.1 with an M4 processor.

You’ve encountered an interesting behavior difference between turbo run lint and turbo watch lint when it comes to detecting changes in your root-level eslint.config.mjs file.

The Problem

  • turbo run lint correctly cache misses when eslint.config.mjs changes
  • turbo watch lint doesn’t rerun when eslint.config.mjs changes, despite it being in the inputs array
  • Moving the file to globalDependencies fixes the issue for turbo watch

Why This Happens

This appears to be related to how turbo watch handles task-level inputs versus global dependencies. The watcher functionality might not be correctly processing the $TURBO_ROOT$ path prefix in the inputs array.

Solutions

1. Use globalDependencies (Recommended)

Since you’ve already found this solution works, this is the most straightforward approach:

{
  "globalDependencies": ["eslint.config.mjs"],
  "tasks": {
    "lint": {
      "outputs": [],
      "inputs": ["$TURBO_DEFAULT$"]
    }
  }
}

2. Use Absolute Path in Inputs

You could try using an absolute path without the $TURBO_ROOT$ variable:

{
  "tasks": {
    "lint": {
      "outputs": [],
      "inputs": ["$TURBO_DEFAULT$", "./eslint.config.mjs"]
    }
  }
}

3. Use a Glob Pattern

Try using a glob pattern that includes the root file:

{
  "tasks": {
    "lint": {
      "outputs": [],
      "inputs": ["$TURBO_DEFAULT$", "*.config.mjs"]
    }
  }
}

Additional Context

This behavior difference between turbo run and turbo watch for root-level inputs might be a subtle bug in Turborepo. The globalDependencies approach is the most reliable solution since it’s specifically designed to handle files that affect multiple packages.

If you’re interested in the technical details, turbo watch uses a different mechanism for file watching than the dependency graph calculation used by turbo run, which might explain the discrepancy in behavior.

The absolute path doesn’t work either, nor does the glob. I’ll stick with the globalDependencies for now. Is this a bug? If so I could file it in the turborepo repo.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.