Skip to content

[finding] check:turbo-task-graph reports turbo's legitimate //#<task> root-task spelling as an unknown package, and its failure text asserts something a dry run contradicts #12465

Description

@yinlianghui

Measured while implementing #12373 (the generic-key widening of check:turbo-task-graph), on c246e4fcf6, session session_01UjM2ia8Av1v5NqfqQEQmC6. Filed unassigned; recording the gap, not claiming it. Nothing is red today — this repo's turbo.json carries no //# key, so the gate's population never meets this shape.

This is in the pre-existing package-scoped arm, not the generic limb #12373 adds. #12373's change does not touch it in either direction.

What was measured

turbo.json was temporarily given a real root task and driven through turbo run (turbo 2.10.10, restored byte-identical afterwards):

"//#lint": { "cache": false }
$ pnpm exec turbo run lint --dry=json
EXIT 0
root task in graph: ['//#lint']
command: node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config

So //#<task> is a working turbo spelling: it binds the root manifest's own script and lands in the graph with a real command.

node scripts/check-turbo-task-graph.mjs on that same tree:

EXIT 1
FAIL: turbo.json carries 1 inert task entry.
- "//#lint" names the package "//", which is not in this pnpm workspace.
Turbo accepts the key SILENTLY — measured on 2.10.10: exit 0, no diagnostic,
and the override never reaches the task graph. Everything this entry was
written to configure is unconfigured.

Why this is worth recording

Two separate defects, and the second is the expensive one:

  1. A false positive on a legitimate shape.splitTaskKey('//#lint') returns { pkg: '//', task: 'lint' }, and // is not a workspace member because it is not a package name at all — it is turbo's reserved token for the repo root. The gate would refuse a correct entry.
  2. The failure text states something the tool contradicts. "the override never reaches the task graph" is true for the misspelled-package case the sentence was written for, and false for this one — the dry run above shows the task in the graph with a real command. A gate whose diagnosis is wrong sends the reader to delete a working entry.

The root manifest's non-membership is load-bearing elsewhere in this gate — #12373's generic limb turns on exactly that fact, and says so in its failure text. // is the one place where that same fact points the other way: the root is not a member, but turbo does address it, under a name the enumerator can never produce.

Suggested disposition (not a decision)

  • Recognise // in splitTaskKey (or before it) as the root manifest, and judge //#<task> against the rootpackage.json's scripts rather than against the member map. This keeps the invariant ("a task nothing can run is inert") for the root too — //#nope where the root declares no nope is still a real finding.
  • ⛔ Do not close it by exempting // from judgement: that trades a false positive for a hole, in the file whose whole point is that nothing else reads it.
  • Whichever lands wants a --self-test case in both directions, since a clean tree contains no //# key to notice a regression with.

Refs: #12046 (the coverage card the gate was built under) · #12373 (measured while implementing it) · scripts/check-turbo-task-graph.mjs


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions