voidzero-dev / voidzero-dev/vite-task

A chained command is split into independently cached sub-tasks, but `output` stays task-level — a hit replays a stale output over a fresh one

Open
#589 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
466
Forks
42
Avg merge
1d 15h
Merged PRs (30d)
19

Description

Summary

When a task's command chains several commands (a && b, or an array), Vite+ caches each sub-command independently, but the task declares a single, task-level output. Every sub-task therefore claims every output file.

On a partially-cached run this is unsound: a sub-task that hits restores its archived copy of the shared outputs over the file a sub-task that missed has just regenerated. The task exits 0 and the artifact on disk is stale.

Ordering makes it visible: with 3 hits and 1 miss on the same task, the missing sub-task in position 2 of 4 leaves a stale output, while the same sub-task in position 4 of 4 leaves the correct one. Clearing the cache (no replay at all) is always correct.

This is not an input-declaration problem. Fixing input so the miss fires correctly does not fix it: the sub-task runs, writes the right file, and a later hit overwrites it.

Reproduction

mkdir vp-chained-output && cd vp-chained-output
npm init -y
npm i -D vite-plus@0.2.7
mkdir -p out

vite.config.ts:

import { defineConfig } from 'vite-plus';

export default defineConfig({
  run: {
    tasks: {
      gen: {
        command: [
          'node -e "require(\'fs\').writeFileSync(\'out/a.txt\', require(\'fs\').readFileSync(\'src-a.txt\',\'utf8\'))"',
          'node -e "require(\'fs\').writeFileSync(\'out/b.txt\', require(\'fs\').readFileSync(\'src-b.txt\',\'utf8\'))"',
        ],
        output: ['out/**'],
      },
    },
  },
});

Automatic tracking matters here: it is what gives each sub-task a different input set, so one can hit
while another misses. With a single explicit input list, every sub-task invalidates together and the bug
stays hidden.

echo v1 > src-a.txt && echo v1 > src-b.txt
npx vp run gen          # miss, out/a.txt = v1, out/b.txt = v1
npx vp run gen          # hit

echo v2 > src-a.txt     # only the first sub-command's input changes
npx vp run gen          # -> "cache miss: 'src-a.txt' modified" for sub-task 1
                        # -> "cache hit"                        for sub-task 2

cat out/a.txt           # expected v2 - observed v1, exit code 0

Observed exactly as above on vite-plus 0.2.7.

Expected

Each sub-task restores only the outputs it produced, or the task is treated as a single cache unit.

Actual

The archive of a hitting sub-task contains files written by its siblings and replays them, silently reverting fresher output. Exit code is 0.

Why it matters in practice

In our monorepo the pattern is common: a generator followed by a fixer, e.g.

kubb generate ./swagger.yml -c=kubb.config.mock.js && npm run fix-generated-mock-imports

Both write under src/gen/mocks/**, which is the task's only declared output. A partial hit yields a half-regenerated SDK with no signal. We found 14 tasks in this shape and had to disable caching on all of them.

Splitting them into one task per command — the obvious workaround, and the one we could apply to a Panda ship chain — is not possible when the second command fixes the output of the first: the fixer has the same files as input and output, so the tracker declines to cache it (Not cached: read and wrote …).

Suggested directions

  • track outputs per sub-task rather than per task, so a sub-task only restores what it wrote; or
  • treat a task with a chained command and more than one output as a single cache unit; or
  • at minimum, refuse to cache (or warn) when a task chains commands and declares outputs that several sub-tasks can write — a loud refusal is far better than a silent stale artifact.

Environment

  • vite-plus 0.2.7
  • macOS darwin 25.5.0, arm64; Node 22.22.0, pnpm 9.15.0

Related

  • voidzero-dev/vite-task#587 — the tracker not observing reads made by a Go binary. Different mechanism, same consequence: a cache hit that hides a wrong result.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the issue with the vite.config.ts task and vp run gen, changing only src-a.txt between runs. Start by tracing cache archive and restore behavior for chained sub-tasks and shared output; done means a partial hit cannot restore a sibling's outputs, or the task is refused caching with a clear warning.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
build-system, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.