ci(turbo): derive the type-check inputs guard from each package's tsc program (#3514) - #4176
Merged
Merged
Conversation
… program (#3514) The hand-maintained `$TURBO_ROOT$` inputs list has been wrong twice for one structural reason: a package's tsc program reaches outside its package directory, where `$TURBO_DEFAULT$` cannot see it and `globalDependencies` is unset. Turbo then replays the previous verdict instead of re-running, and CI persists `.turbo/cache`, so the stale green travels. scripts/__tests__/turbo-type-check-inputs.test.ts derives the requirement instead of restating it: it walks every workspace package's real type-check program (the projects its `type-check` script drives, their include/files root set, their `extends` chain, and `tsc -b` project references) via TypeScript's own config parser, collects every file landing outside the package directory, and asserts turbo's inputs cover each one. Its first run named a third, wider instance: 39 packages extend the repo-root `tsconfig.json` and `examples/byo-backend-console` extends `tsconfig.base.json`, neither of which was hashed. Both entries ship here. Measured on this branch, @object-ui/types, pre-fix inputs: warm the cache green, plant a real TS2688 in the root tsconfig.json, re-run -> `FULL TURBO` in 44ms, exit 0, while --force on the same tree exits 2. With the fix the same tree is a cache miss and exits 1 naming the error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
yinlianghui
marked this pull request as ready for review
August 10, 2026 17:32
This was referenced Aug 10, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3514
Premise check first — half of the card had already shipped
The card's "Suggested fix" one-liner (
$TURBO_ROOT$/vitest.config.mtsin thetype-checkinputs) is already onmain: the blocking PR #3513 merged as 5534087 and carried it, exactly as the PM re-scope comment of 2026-08-07 ruled. So there was no one-liner left to add, and the card's own reproduce script inverts on this tip — planting a type error in the rootvitest.config.mtsnow moves the cache key rather than replaying a stale green.What survived is the durability half, and the delegated ruling asked for the derived guard rather than pinned pairs. That is what this PR is. It also found a third instance of the class, live on
maintoday.The mechanism
Turbo hashes a task from its
inputs.$TURBO_DEFAULT$covers only files inside the package directory, andglobalDependenciesis unset — so any file a package's tsc program reads from elsewhere in the repo is invisible to the cache key. When such a file changes, turbo does not re-run the task; it replays the previous verdict..github/workflows/ci.ymlpersists.turbo/cachethroughactions/cache, so one poisoned entry rides into later runs.Twice now the list was fixed by hand after the fact:
scripts/vite-*.ts, thenvitest.config.mts(#3476 / #3513). Two hand-fixes for one structural cause is this repo's threshold for a gate.What ships
1.
scripts/__tests__/turbo-type-check-inputs.test.ts— derives the requirement instead of restating it. For every workspace package with atype-checkscript it resolves the tsc projects that script actually drives (baretsc --noEmit,tsc -p, andtsc -bplus its project references), parses each through TypeScript's ownparseJsonSourceFileConfigFileContentsofileNamesis the real program root set, collects theextendschain viaextendedSourceFiles, and reports every file that lands outside the package directory. Each one must be matched by a$TURBO_ROOT$input, or the test reds naming the package, the file and the entry to add.Three companion assertions keep the guard from decaying into the thing it guards against: the sweep must find packages and a non-empty out-of-package set (a silently-degraded derivation would pass vacuously); every
$TURBO_ROOT$entry must match a file on disk; and every entry must still be required by some package's program, so the list can only shrink.Stated narrowings, in the file's docblock: it asserts over program root files (config
include/files/extends/references), not the transitive import closure — building 40+ real programs needs a built workspace and minutes of CPU, and the narrowing is sound for this failure because a composite project must list every file in its program, which is why both known drifts wereincludeentries. Glob syntax it does not implement throws rather than being approximated, since an approximated match that comes out true is a file waved through while turbo does not hash it. Unparsedtype-checkscript shapes and unknown workspace glob shapes throw for the same reason.2.
turbo.json— the two entries the guard's first run named:39 packages' tsconfigs extend the repo-root
tsconfig.json;examples/byo-backend-consoleextends the roottsconfig.base.json. Compiler options are as load-bearing as source — flippingstrictthere changes every package's verdict — and neither file was hashed.Measured
Cache key, before the fix. Mutating the root
tsconfig.jsonleaves the hash untouched:End to end,
@object-ui/types, pre-fix inputs — warm the cache green, plant a realTS2688in the roottsconfig.json, re-run:The verdict depended on cache state, not on the code. With the fix, same tree, same defect:
The card's own file, for symmetry. With
$TURBO_ROOT$/vitest.config.mtspresent, touching the root Vitest config moves@object-ui/console's hash (bed674f43a6c248ftoaea440d108ddd00e); with that entry removed it is frozen ate4f708651f5bce3c. #3513's entry is genuinely load-bearing, and this guard would have caught its absence.Reverse verification
Each
$TURBO_ROOT$entry removed in turn, and a phantom entry added:Gates
Changeset: none owed —
check-changeset-presence.mjsarbitrates: "No source of a released package changed in this range, so no changeset is owed." (turbo.jsonplus a test, no released packagesrc/.)Generated by Claude Code