Skip to content

turbo lint inputs have the same out-of-package hole as #3514 / #4178 — the root eslint.config.js that IS the lint program is not hashed; measured frozen at 79872f192ee4828c #4184

Description

@yinlianghui

Found by the task sweep #4178 asked for ("any OTHER turbo task with out-of-package program files — MEASURE"). Filed unassigned; not fixed in PR for #4178, which is scoped to the test task.

The gap

#3514 (PR #4176) established the mechanism and #4178 repeated it on test: turbo hashes a task from its inputs, $TURBO_DEFAULT$ covers only files inside the package directory, and globalDependencies is unset — so any file the task reads from elsewhere in the repo is invisible to the cache key, and turbo replays the previous verdict instead of re-running.

lint is the third instance, and it is the starkest of the three: the task declares no inputs at all, so it runs on turbo's default, and the file that is the lint program — the repo-root flat config eslint.config.js — lives outside every package.

"lint": {
  "outputs": [],
  "cache": true
}

Every package's lint script is ESLint reading that one root config. Change a rule there and no package's lint hash moves.

Measured

On claude/issue-4178-turbo-test-inputs (which changes only the test task, so this measurement is of main's lint):

$ npx turbo run lint --filter @object-ui/core --dry=json
  baseline                              79872f192ee4828c
  after touching eslint.config.js       79872f192ee4828c     <- frozen

Not local-only: .github/workflows/lint.yml runs turbo run lint and persists .turbo/cache through actions/cache (turbo-${{ runner.os }}-${{ github.sha }}, restore key turbo-${{ runner.os }}-), so one poisoned entry rides into later runs — the same travel path #3514 documented.

This is the "looks like enforcement, isn't" class the repo has already paid for repeatedly: adding or tightening an ESLint rule can land, go green on cached verdicts computed before the rule existed, and never actually run against the code it was written for.

Suggested shape

Symmetric with what #4176 and #4178 landed:

  1. Give lint an explicit inputs list including $TURBO_ROOT$/eslint.config.js and whatever that config's own program pulls in — note eslint-rules/ holds this repo's local plugin, so the config's import chain reaches there too, and those rule implementations are as load-bearing as the config.
  2. Derive rather than pin. After turbo test inputs have the same out-of-package hole #3514 closed for type-check — the root vitest.config.mts every package re-exports is not hashed #4178 the shared plumbing already exists at scripts/__tests__/helpers/turbo-inputs.ts (workspace discovery, $TURBO_ROOT$ extraction, glob matching), and scripts/__tests__/helpers/vitest-config-program.ts is a worked example of walking a JS config's import chain plus its designated files. A lint derivation would walk eslint.config.js's relative import chain the same way. Whether the flat config's files / ignores globs need any treatment beyond the import chain is the open design question.

Whether the fix is the inputs list alone or the list plus a derivation guard is a maintainer/PM call, as it was for #3514 and #4178.

Related


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions