Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

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

Closed

Description

@yinlianghui

Found while implementing #3514 (PR #4176). Filed unassigned; not fixed there because that card is scoped to the type-check task.

The gap

#3514 established the mechanism: turbo hashes a task from its inputs, $TURBO_DEFAULT$ covers only files inside the package directory, and globalDependencies is unset — so any file a task reads from elsewhere in the repo is invisible to the cache key, and turbo replays the previous verdict instead of re-running. CI persists .turbo/cache through actions/cache, so the stale result travels.

The test task has exactly that shape, and every entry in its list is package-relative:

"inputs": [
  "src/**", "test/**", "tests/**",
  "vitest.config.*", "vitest.setup.*",
  "tsconfig*.json", "package.json",
  "$TURBO_DEFAULT$", "!**/*.md", "!**/CHANGELOG.md"
]

vitest.config.* and vitest.setup.* look like they cover the Vitest configuration, but they resolve inside the package. The files that actually decide how a package's tests run live at the repo root, and every package reaches them:

$ cat packages/core/vitest.config.ts
import rootConfig from '../../vitest.config.mts';
export default rootConfig;

The root vitest.config.mts owns the unit / dom / dom-heavy project split, sharedExclude, the domTsTests and heavyDomTests lists, and the alias table. The four root vitest.setup.* files decide which package graphs are registered before a test renders. None of them is hashed by any package's test task.

Measured

On claude/issue-3514-turbo-inputs-guard, @object-ui/core:

$ npx turbo run test --filter @object-ui/core --dry=json
  baseline                                    2e2087e2c30ef125
  after touching root vitest.config.mts       2e2087e2c30ef125     <- frozen
  after touching root vitest.setup.base.ts    2e2087e2c30ef125     <- frozen

The cache key does not move. The equivalent measurement on type-check before #4176 produced a FULL TURBO replay in 44ms over a real TS2688, with --force on the same tree exiting 2 — so this is the same defect, not a theoretical one.

Arguably worse than the type-check instance: a package's type-check reaches out only where a tsconfig says so, whereas every package's test run goes through the root Vitest config by construction.

Suggested shape

Symmetric with what #4176 landed, plus the guard question:

  1. Add the root files to the test task's inputs ($TURBO_ROOT$/vitest.config.mts, $TURBO_ROOT$/vitest.setup.*).
  2. scripts/__tests__/turbo-type-check-inputs.test.ts (from ci(turbo): derive the type-check inputs guard from each package's tsc program (#3514) #4176) is already written against a single TASK constant and derives out-of-package files from tsconfig programs. The test task's equivalent derivation is different in kind — it would have to follow the vitest.config.ts re-export chain rather than tsconfig include — so whether that is worth deriving or is better served by pinned pairs is a separate judgement, not a copy of guard: turbo type-check 的手维护 inputs 名单已两次因"包程序越出包目录"失真 —— 评估从 tsconfig include 派生或加钉扎测试 #3514's answer.

Whether the fix is the one-liner alone or the one-liner plus a guard is a maintainer/PM call, the same way #3514's was.

Related


Generated by Claude Code

Activity

  1. self-assigned this
    on Aug 10, 2026
  2. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    ContributorAuthor

    Claim: PM loop round 6 (rolling slot refill; the #3514 successor — same cache-poisoning class, test task side)
    Session: session_017Qqyix2QcnpUC9XeYVDzx3 · Branch: claude/issue-4178-turbo-test-inputs · Worktree: objectui-issue-4178
    File surface: turbo.json (test task inputs) + a derivation extension in scripts/__tests__/ (sibling or extension of #4176's guard — read its architecture first; the filing notes the derivation is DIFFERENT IN KIND: vitest re-export chains, not tsconfig includes). Serial constraints: PR #4176 (armed, unmerged) owns turbo.json + the guard file — stack on claude/issue-3514-turbo-inputs-guard if it has not merged at start; disjoint from #4167/#4145/#2794/#2627 in flight.
    Container & model: S, mode:subagent, model: opus

    Direction (the #3514 ruling's logic carries over): the measured entries ship (root vitest.config.mts + the four root vitest.setup.* files the per-package configs re-export) AND the derivation guard extends to the test task — walking each package's vitest config import chain (the filing says every per-package config literally import rootConfig from '../../vitest.config.mts'; derive the chain, don't hardcode the observation). Reproduce the frozen-hash baseline (@object-ui/core#test at the filing's hash after touching both files) → fix flips it to a moving hash + cache miss, per #4176's evidence-chain shape. If chain-walking proves brittle (dynamic imports, non-literal paths), the honest fallback is pinned entries + a docblock narrowing, reported as such.


    Generated by Claude Code

  3. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    ContributorAuthor

    ACCEPT (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4188.

    Measurement corrected the filing twice (10 out-of-package files, not 5; the "every config re-exports" claim false for 16 of 39) and the derivation is built where the reality is: Vitest's own config resolution mirrored and verified against the real binary, transitive relative imports, AND file-valued options — the last being load-bearing since two packages reach out ONLY via setupFiles, which the card's proposed import-chain walk would have missed. The shared turbo-side plumbing extracted so #4176's guard and this one cannot drift (its unchanged 43 the preservation evidence). The end-to-end run has the full evidence ladder including turbo replaying a PREVIOUS run's captured output timestamp-and-all — the stale-green mechanism made visible. The decisive reverse case (a probe file in a zero-derivation package, named by the guard with nothing in the PR mentioning it) proves derivation over enumeration. The blast-radius narrowing (ci.yml doesn't run turbo run test today — local + future CI only) is honestly stated where the two sibling holes are NOT so limited.

    Which is why the siblings move NOW: #4184 (lint: NO inputs, LIVE in CI with persisted cache) and #4185 (build: NO inputs, dist feeding every downstream task) are packed into one stacked dispatch on this branch. The plugin-editor red (alias gap, independent of this card) gets its own card referencing #3240's ruled direction. Flipping ready + auto-merge.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions