Skip to content

[P0] Make ordinary Vitest commands fail on absorbed worker-start failures #690

Description

@mohanagy

Parent: #654
Target branch: next
Depends on: #688 merged into next
Blocks: #654 completion, merge of #681 and #680, and start of #657

Outcome

Make the repository’s ordinary complete-suite commands fail whenever Vitest logs a forks-worker startup/handshake failure, even if the forks pool respawns a worker and Vitest later exits zero with a green summary.

The protected prerelease workflow already enforces this at release time through assert-clean-vitest-log.mjs. This issue extends the same evidence standard to normal developer and protected-CI test commands so a green summary cannot conceal the symptom tracked by #654.

Confirmed evidence

The #654 investigation established:

  • vitest.config.ts uses the forks pool with maxWorkers: 4 and no configured test.retry;
  • one local npm run test:run invocation logged Failed to start forks worker / Timeout waiting for worker to respond for eight files;
  • the same invocation later counted those files as passed after internal worker respawn;
  • final result: 182 files, 2,131 tests passed, two skipped, zero assertion failures;
  • therefore command exit code zero and a green final tally do not prove worker startup was clean;
  • raw GitHub Actions logs from the dedicated-runner attempts were searched directly and contained zero occurrences, so the dedicated-runner distinction remains valid.

Known signatures:

Failed to start forks worker
Timeout waiting for worker to respond

Scope

1. Inventory the authoritative test entrypoints

Identify every command and workflow that runs the complete unit suite or coverage suite, including:

  • package.json scripts;
  • pull-request and push CI;
  • prerelease validation;
  • stable release validation;
  • developer documentation;
  • any benchmark/release wrapper that assumes a green Vitest summary means a clean run.

Determine which command is the authoritative complete-suite gate.

The repository must end with one clear ordinary command contract rather than release-only enforcement.

2. Design a cross-platform suite runner

Use a Node-based wrapper or equivalent cross-platform mechanism.

The runner must:

  1. spawn the intended Vitest command;
  2. stream stdout and stderr to the terminal in real time;
  3. retain complete output in a temporary log;
  4. preserve the child process exit status and signal;
  5. scan the complete log for known worker-start signatures;
  6. fail when the child fails;
  7. fail when the scanner finds a signature even if the child exits zero;
  8. return zero only when the child succeeds and the log is clean;
  9. report exact signature, count, and matching lines with context;
  10. clean up temporary files on success while retaining or exposing them on failure according to documented policy;
  11. behave consistently on Ubuntu, macOS, and Windows;
  12. forward relevant arguments and termination signals safely.

Do not use Bash-only PIPESTATUS as the ordinary cross-platform implementation.

3. Reuse one scanner contract

Do not create divergent signature lists for local, CI, stable release, and prerelease validation.

Choose and document one source of truth for known absorbed-failure signatures.

Possible directions:

  • move the existing scanner from .github/scripts/ into a general repository script and update release workflows;
  • keep the scanner module where it is but expose a stable reusable API and ordinary runner;
  • another evidence-supported structure with one canonical signature policy.

Do not weaken the prerelease workflow protection already merged through #688.

4. Wire ordinary commands and protected CI

The normal commands used by developers and CI must enforce the scanner.

Prefer preserving familiar public script names:

npm run test:run
npm run test:coverage

Internal raw Vitest scripts may be introduced with explicit names if needed, but users and protected CI must not accidentally call the unguarded form.

Update every protected workflow that should use the authoritative command.

Do not duplicate expensive complete-suite execution merely to scan logs.

5. Diagnostics and artifacts

When protected CI fails:

  • retain the complete raw log as a bounded diagnostic artifact;
  • name it with run, attempt, OS, Node version, and suite kind;
  • print signature counts in the job summary or log;
  • distinguish ordinary test failure from absorbed worker-start detection;
  • do not upload secrets, npm credentials, or arbitrary environment dumps.

For local runs, print the temporary log path on failure.

Explicit non-goals

  • Do not add automatic retries.
  • Do not make Vitest’s internal respawn behavior count as success.
  • Do not increase timeouts merely to avoid signatures.
  • Do not force --maxWorkers=1 globally.
  • Do not skip or quarantine tests.
  • Do not change graph, retrieval, context-pack, extraction, watcher, MCP, installer, or product behavior.
  • Do not mix deterministic watcher/backoff changes from sibling issue [P0] Replace uncontrolled wall-clock assertions in watch and refresh tests with deterministic scheduling #689 into this PR.
  • Do not claim that this detector alone establishes the root cause of local worker startup starvation.

Required tests

Scanner and wrapper

  • clean log plus child exit zero → success;
  • worker-start signature plus child exit zero → failure;
  • handshake-timeout signature plus child exit zero → failure;
  • both signatures → failure with both counts;
  • child exit non-zero and clean log → original failure preserved;
  • child exit non-zero plus signature → both causes reported;
  • child terminated by signal → signal-aware failure;
  • missing/unreadable log → fail closed;
  • similar unrelated text → no false positive;
  • output is streamed while retained;
  • arguments are forwarded safely;
  • spaces and Unicode in temporary paths;
  • cleanup/retention behavior;
  • Windows process invocation and path handling.

Workflow and script policy

  • npm run test:run uses the guarded runner;
  • npm run test:coverage uses the guarded runner;
  • protected CI uses the authoritative guarded commands;
  • prerelease and stable release validation use the same canonical detector;
  • no protected workflow invokes the raw unguarded complete suite accidentally;
  • failure diagnostic artifacts are bounded and named deterministically;
  • no retries, test skips, or worker reductions are introduced.

Validation

At minimum run:

npm ci
npm run typecheck
npm run build
npm run test:run
npm run test:coverage
npm run verify:pack-parity
npm pack --dry-run
npm run registry:validate
npm run release:verify

Also run deterministic injected probes:

  1. a fake child that prints a green Vitest summary plus a worker-start signature and exits zero;
  2. a fake child that exits non-zero without a signature;
  3. a clean fake child;
  4. signal termination where supported.

Run protected CI across Ubuntu, macOS, and Windows on supported Node 20/22 lines.

For each lane report separately:

  • job conclusion;
  • raw worker-start signature count;
  • raw handshake-timeout signature count;
  • control sample detection result;
  • test and coverage exit statuses.

Acceptance criteria

  • One authoritative ordinary complete-suite contract is documented and used by protected CI.
  • npm run test:run fails on an absorbed worker-start signature even when Vitest exits zero.
  • npm run test:coverage has the same behavior.
  • Child exit codes and signals are preserved accurately.
  • Full output remains visible and diagnostic logs are retained on failure.
  • Local and workflow enforcement share one canonical scanner/signature policy.
  • No unguarded complete-suite path remains in protected workflows.
  • Cross-platform tests pass on Ubuntu, macOS, and Windows.
  • No retry, skip, timeout inflation, or global single-worker workaround is introduced.
  • Exact-head six-lane CI passes with zero raw signature occurrences.
  • Injected controls prove the detector is active in every supported lane.
  • No unrelated product or roadmap scope enters the diff.

Rollback

Restore the previous script wiring while retaining the detector and raw evidence for diagnosis. A rollback must not claim that green Vitest summaries prove clean worker startup. If the wrapper itself is defective, repair or replace it before #654 can complete.

Agent handoff

Start from current origin/next after PR #688. Inventory every full-suite invocation before changing scripts. Implement one cross-platform authoritative runner and one canonical signature policy. Return the command graph before/after, injected-control evidence, child-status behavior, files changed, exact protected lanes, raw-log counts, and remaining uncertainty. Stop after this issue.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions