From 8334fbf7efd9f983bb0712c13783e91096f25390 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 14:45:28 +0000 Subject: [PATCH 1/8] fix(deps): pin @vitejs/devtools-vitest's vitest peer to the workspace range vitest 5.0.0 (published 2026-09-03 12:24 UTC) breaks every lockfile-less npm install on npm 10.9: vite 8 has an optional peer on @vitejs/devtools, whose devtools-vitest plugin declares peerDependencies.vitest = "*". That wildcard now resolves to vitest 5 even though every workspace range is ^4.1.0, and arborist crashes building the peer set with "Cannot read properties of null (reading 'edgesOut')". npm 11 copes, which is why only the Node 22 Fresh Install and Publish Fresh Install Build jobs went red (on main too, from run 33765746872 onward). Override that one peer edge to the workspace's own vitest range. With it a fresh npm 10.9.2 install resolves to vitest 4.1.11, matching the lockfile; the lockfile itself is unchanged. The $vitest reference form is not used because npm 10.9 fails to resolve it on peer edges. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MB3K5bK7Jc5HM92fsZRUyS --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 48ee95c4b..8fe06b720 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,9 @@ "js-yaml": "^4.3.0", "postcss": "^8.5.23", "shell-quote": "^1.10.0", - "tar": "^7.5.22" + "tar": "^7.5.22", + "@vitejs/devtools-vitest": { + "vitest": "^4.1.0" + } } } From e6f02bb288950d34b4e008d6e0ca69ad03561d83 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 14:45:29 +0000 Subject: [PATCH 2/8] chore: record trajectory for the vitest peer override Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MB3K5bK7Jc5HM92fsZRUyS --- .../2026-09/traj_pwl779phg4f3.trace.json | 25 ++++++++ .../2026-09/traj_pwl779phg4f3/summary.md | 38 ++++++++++++ .../2026-09/traj_pwl779phg4f3/trajectory.json | 58 +++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 .agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3.trace.json create mode 100644 .agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/summary.md create mode 100644 .agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/trajectory.json diff --git a/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3.trace.json b/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3.trace.json new file mode 100644 index 000000000..36a06dcb3 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3.trace.json @@ -0,0 +1,25 @@ +{ + "version": "1.0.0", + "id": "82c0908d-132f-4835-b47e-e76385858bb9", + "timestamp": "2026-09-03T14:45:29.548Z", + "trajectory": "traj_pwl779phg4f3", + "files": [ + { + "path": "package.json", + "conversations": [ + { + "contributor": { + "type": "ai" + }, + "ranges": [ + { + "start_line": 155, + "end_line": 163, + "revision": "8334fbf7efd9f983bb0712c13783e91096f25390" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/summary.md b/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/summary.md new file mode 100644 index 000000000..fd02bbb1d --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/summary.md @@ -0,0 +1,38 @@ +# Trajectory: Pin @vitejs/devtools-vitest's vitest peer so fresh installs survive vitest 5 + +> **Status:** ✅ Completed +> **Confidence:** 90% +> **Started:** September 3, 2026 at 02:45 PM +> **Completed:** September 3, 2026 at 02:45 PM + +--- + +## Summary + +Nested npm override pins @vitejs/devtools-vitest's wildcard vitest peer to the workspace range; verified fresh install with npm 10.9.2 against main's manifests + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Nested override on @vitejs/devtools-vitest rather than a root vitest override +- **Chose:** Nested override on @vitejs/devtools-vitest rather than a root vitest override +- **Reasoning:** Only the wildcard peer edge is rewritten; the root range and every workspace range stay untouched, and npm 10.9's $vitest reference resolution fails on peer edges so the range is literal + +--- + +## Chapters + +### 1. Work +*Agent: default* + +- Nested override on @vitejs/devtools-vitest rather than a root vitest override: Nested override on @vitejs/devtools-vitest rather than a root vitest override + +--- + +## Artifacts + +**Commits:** 8334fbf +**Files changed:** 1 diff --git a/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/trajectory.json b/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/trajectory.json new file mode 100644 index 000000000..9e16628ae --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/trajectory.json @@ -0,0 +1,58 @@ +{ + "id": "traj_pwl779phg4f3", + "version": 1, + "task": { + "title": "Pin @vitejs/devtools-vitest's vitest peer so fresh installs survive vitest 5" + }, + "status": "completed", + "startedAt": "2026-09-03T14:45:26.950Z", + "completedAt": "2026-09-03T14:45:29.522Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-09-03T14:45:28.154Z" + } + ], + "chapters": [ + { + "id": "chap_uhzfixybx5dw", + "title": "Work", + "agentName": "default", + "startedAt": "2026-09-03T14:45:28.154Z", + "endedAt": "2026-09-03T14:45:29.522Z", + "events": [ + { + "ts": 1788446728155, + "type": "decision", + "content": "Nested override on @vitejs/devtools-vitest rather than a root vitest override: Nested override on @vitejs/devtools-vitest rather than a root vitest override", + "raw": { + "question": "Nested override on @vitejs/devtools-vitest rather than a root vitest override", + "chosen": "Nested override on @vitejs/devtools-vitest rather than a root vitest override", + "alternatives": [], + "reasoning": "Only the wildcard peer edge is rewritten; the root range and every workspace range stay untouched, and npm 10.9's $vitest reference resolution fails on peer edges so the range is literal" + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Nested npm override pins @vitejs/devtools-vitest's wildcard vitest peer to the workspace range; verified fresh install with npm 10.9.2 against main's manifests", + "approach": "Standard approach", + "confidence": 0.9 + }, + "commits": [ + "8334fbf" + ], + "filesChanged": [ + "package.json" + ], + "projectId": "AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "e87f186938d125811c74341d4371f4f021115b01", + "endRef": "8334fbf7efd9f983bb0712c13783e91096f25390", + "traceId": "82c0908d-132f-4835-b47e-e76385858bb9" + } +} \ No newline at end of file From 26998a336e74816c8391076a97b84f97de4bc6ba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 3 Sep 2026 14:47:11 +0000 Subject: [PATCH 3/8] style: auto-format with Prettier --- .trajectories/compacted/release-11.10.2.json | 19 ++++--------------- .trajectories/compacted/release-11.10.2.md | 8 +++++++- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/.trajectories/compacted/release-11.10.2.json b/.trajectories/compacted/release-11.10.2.json index 151c30136..ad207dfb6 100644 --- a/.trajectories/compacted/release-11.10.2.json +++ b/.trajectories/compacted/release-11.10.2.json @@ -3,9 +3,7 @@ "version": 1, "type": "compacted", "compactedAt": "2026-09-03T06:52:02.784Z", - "sourceTrajectories": [ - "traj_7yref3wye283" - ], + "sourceTrajectories": ["traj_7yref3wye283"], "dateRange": { "start": "2026-09-02T10:25:41.382Z", "end": "2026-09-02T12:08:45.907Z" @@ -13,9 +11,7 @@ "summary": { "totalDecisions": 7, "totalEvents": 11, - "uniqueAgents": [ - "default" - ] + "uniqueAgents": ["default"] }, "decisionGroups": [ { @@ -103,12 +99,5 @@ "tests/relayflows/cases/1638-attach-input-replay/case.json", "tests/relayflows/cases/1638-attach-input-replay/run.mjs" ], - "commits": [ - "e85f4ca23", - "bf66a2e86", - "13971aa8f", - "1191af9bd", - "cefc1ab4d", - "2559e668a" - ] -} \ No newline at end of file + "commits": ["e85f4ca23", "bf66a2e86", "13971aa8f", "1191af9bd", "cefc1ab4d", "2559e668a"] +} diff --git a/.trajectories/compacted/release-11.10.2.md b/.trajectories/compacted/release-11.10.2.md index e8680f9be..fc7dee8b6 100644 --- a/.trajectories/compacted/release-11.10.2.md +++ b/.trajectories/compacted/release-11.10.2.md @@ -1,6 +1,7 @@ # Trajectory Compaction: Sep 2, 2026 - Sep 2, 2026 ## Summary + - Sessions: 1 - Decisions: 7 - Events: 11 @@ -9,6 +10,7 @@ - Commits: 6 ## Api + - Treat Relaycast publication and recipient reachability as separate observations -> Treat Relaycast publication and recipient reachability as separate observations (traj_7yref3wye283) - Keep the reachability probe outside the publication timeout -> Keep the reachability probe outside the publication timeout (traj_7yref3wye283) - Keep the synchronous reachability snapshot bounded at five seconds -> Keep the synchronous reachability snapshot bounded at five seconds (traj_7yref3wye283) @@ -16,13 +18,17 @@ - Cancel reachability observation when publication fails -> Cancel reachability observation when publication fails (traj_7yref3wye283) ## Security + - Resolve Relaycast @self before reachability probing -> Resolve Relaycast @self before reachability probing (traj_7yref3wye283) ## Other + - Treat legacy away as reachable -> Treat legacy away as reachable (traj_7yref3wye283) ## Key Learnings + - None ## Key Findings -- None \ No newline at end of file + +- None From 67cea9cd4bded7f4feb37d7499e508f8285c32af Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 14:50:57 +0000 Subject: [PATCH 4/8] test(relayflows): prove the fresh-install fix red/green with a pinned npm 10.9.2 The proof-gate allowlist fails closed and does not list the root package.json, so the override counts as a runtime change and needs a case. The case copies only the workspace manifests out of the exact target checkout (no lockfile) and asks npx npm@10.9.2 to build an ideal tree: base crashes with the arborist edgesOut error, head resolves vitest 4.x. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MB3K5bK7Jc5HM92fsZRUyS --- .../1649-fresh-install-vitest-peer/case.json | 21 +++ .../1649-fresh-install-vitest-peer/run.mjs | 169 ++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 tests/relayflows/cases/1649-fresh-install-vitest-peer/case.json create mode 100644 tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs diff --git a/tests/relayflows/cases/1649-fresh-install-vitest-peer/case.json b/tests/relayflows/cases/1649-fresh-install-vitest-peer/case.json new file mode 100644 index 000000000..da54cb3d0 --- /dev/null +++ b/tests/relayflows/cases/1649-fresh-install-vitest-peer/case.json @@ -0,0 +1,21 @@ +{ + "version": 1, + "id": "1649-fresh-install-vitest-peer", + "kind": "bugfix", + "title": "A lockfile-less npm install on npm 10.9 survives the vitest 5 wildcard peer", + "runner": { + "command": ["node", "tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs"] + }, + "requirements": [], + "timeoutSeconds": 900, + "expected": { + "base": { + "outcome": "bug", + "signature": "fresh_install_crashes_on_wildcard_vitest_peer" + }, + "head": { + "outcome": "fixed", + "signature": "fresh_install_resolves_vitest_4" + } + } +} diff --git a/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs b/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs new file mode 100644 index 000000000..c8b5a435b --- /dev/null +++ b/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs @@ -0,0 +1,169 @@ +// RelayFlow proof for #1649: a lockfile-less `npm install` on npm 10.9 must +// survive `@vitejs/devtools-vitest`'s wildcard `vitest` peer. +// +// Since vitest 5.0.0 was published (2026-09-03 12:24 UTC), that wildcard +// resolves to vitest 5 even though every workspace range is ^4, and npm 10.9's +// arborist crashes building the peer set. The head arm carries a root +// `overrides` entry that pins that one peer edge to the workspace range. +// +// The probe copies ONLY the workspace manifests (root + packages/*) out of the +// exact target checkout into a scratch directory, without the lockfile, and +// asks a pinned npm 10.9.2 to build an ideal tree (`--package-lock-only`, no +// scripts). The runner's own npm version is irrelevant: `npx npm@10.9.2` is +// the resolver under test on both arms. +import { execFileSync, spawnSync } from 'node:child_process'; +import { copyFile, mkdir, mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const CASE_ID = '1649-fresh-install-vitest-peer'; +const NPM_UNDER_TEST = 'npm@10.9.2'; +const CRASH_MARKER = "Cannot read properties of null (reading 'edgesOut')"; +const COMMAND_TIMEOUT_MS = 10 * 60 * 1000; + +const targetDir = requiredDirectory('RELAY_PR_PROOF_TARGET_DIR'); +const harnessDir = requiredDirectory('RELAY_PR_PROOF_HARNESS_DIR'); +const resultPath = requiredValue('RELAY_PR_PROOF_RESULT_PATH'); +const arm = requiredValue('RELAY_PR_PROOF_ARM'); + +if (arm !== 'base' && arm !== 'head') { + throw new Error(`RELAY_PR_PROOF_ARM must be base or head, received ${JSON.stringify(arm)}.`); +} + +const expectedSha = + arm === 'base' ? process.env.RELAY_PR_PROOF_BASE_SHA : process.env.RELAY_PR_PROOF_HEAD_SHA; +if (!expectedSha) throw new Error(`Missing expected ${arm} SHA.`); +const targetSha = execFileSync('git', ['-C', targetDir, 'rev-parse', 'HEAD'], { + encoding: 'utf8', +}).trim(); +if (targetSha !== expectedSha) { + throw new Error(`Target checkout ${targetSha} does not match exact ${arm} SHA ${expectedSha}.`); +} + +const runnerPath = fileURLToPath(import.meta.url); +if (!isWithin(harnessDir, runnerPath)) { + throw new Error('The RelayFlow runner must execute from the exact-head harness checkout.'); +} + +const probeDir = await mkdtemp(path.join(os.tmpdir(), `relayflow-${CASE_ID}-`)); +try { + const copied = await copyWorkspaceManifests(targetDir, probeDir); + console.log(`Copied ${copied} workspace manifests from ${targetSha} into ${probeDir}`); + + const rootManifest = JSON.parse(await readFile(path.join(probeDir, 'package.json'), 'utf8')); + const rootRange = rootManifest.devDependencies?.vitest ?? rootManifest.dependencies?.vitest; + if (typeof rootRange !== 'string' || !rootRange.startsWith('^4')) { + throw new Error( + `This proof assumes the workspace pins vitest ^4; the root manifest asks for ${JSON.stringify(rootRange)}.` + ); + } + + const install = spawnSync( + 'npx', + [ + '--yes', + '--package', + NPM_UNDER_TEST, + '--', + 'npm', + 'install', + '--package-lock-only', + '--ignore-scripts', + '--no-audit', + '--no-fund', + '--loglevel', + 'error', + ], + { + cwd: probeDir, + env: { ...process.env, npm_config_update_notifier: 'false' }, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + timeout: COMMAND_TIMEOUT_MS, + } + ); + if (install.error) throw new Error(`${NPM_UNDER_TEST} could not start: ${install.error.message}`); + const stderr = install.stderr ?? ''; + const stdout = install.stdout ?? ''; + console.log(`${NPM_UNDER_TEST} install exited with ${install.status ?? `signal ${install.signal}`}`); + if (stderr.trim()) console.log(stderr.trim().split('\n').slice(-8).join('\n')); + + let outcome; + let signature; + let details; + if (install.status !== 0 && stderr.includes(CRASH_MARKER)) { + outcome = 'bug'; + signature = 'fresh_install_crashes_on_wildcard_vitest_peer'; + details = `${NPM_UNDER_TEST} could not build an ideal tree from the ${arm} manifests: arborist crashed with "${CRASH_MARKER}" while loading the peer set that @vitejs/devtools-vitest's wildcard vitest peer pulls in.`; + } else if (install.status === 0) { + const lock = JSON.parse(await readFile(path.join(probeDir, 'package-lock.json'), 'utf8')); + const resolved = lock.packages?.['node_modules/vitest']?.version; + if (typeof resolved !== 'string' || !resolved.startsWith('4.')) { + throw new Error( + `Install succeeded but resolved vitest ${JSON.stringify(resolved)}; expected a 4.x matching ${rootRange}.` + ); + } + outcome = 'fixed'; + signature = 'fresh_install_resolves_vitest_4'; + details = `${NPM_UNDER_TEST} built an ideal tree from the ${arm} manifests without a lockfile and resolved vitest ${resolved} for the workspace range ${rootRange}.`; + } else { + throw new Error( + `Unexpected ${NPM_UNDER_TEST} outcome (exit ${install.status ?? install.signal}): ${(stderr || stdout) + .trim() + .split('\n') + .slice(-5) + .join(' | ')}` + ); + } + + await mkdir(path.dirname(resultPath), { recursive: true }); + await writeFile( + resultPath, + `${JSON.stringify({ version: 1, caseId: CASE_ID, arm, outcome, signature, details })}\n`, + 'utf8' + ); +} finally { + await rm(probeDir, { recursive: true, force: true }); +} + +async function copyWorkspaceManifests(sourceRoot, destinationRoot) { + await copyFile(path.join(sourceRoot, 'package.json'), path.join(destinationRoot, 'package.json')); + let count = 1; + const packagesDir = path.join(sourceRoot, 'packages'); + for (const entry of await readdir(packagesDir, { withFileTypes: true })) { + if (!entry.isDirectory()) continue; + const manifest = path.join(packagesDir, entry.name, 'package.json'); + let source; + try { + source = await readFile(manifest); + } catch (error) { + if (error?.code === 'ENOENT') continue; + throw error; + } + const destination = path.join(destinationRoot, 'packages', entry.name); + await mkdir(destination, { recursive: true }); + await writeFile(path.join(destination, 'package.json'), source); + count += 1; + } + return count; +} + +function requiredValue(name) { + const value = process.env[name]?.trim(); + if (!value) throw new Error(`Missing required environment variable ${name}.`); + return value; +} + +function requiredDirectory(name) { + return path.resolve(requiredValue(name)); +} + +function isWithin(directory, candidate) { + const relative = path.relative(directory, candidate); + return ( + relative === '' || + (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative)) + ); +} From 7487cb7512c014baff3b2dc9e61310a497565d99 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 15:04:47 +0000 Subject: [PATCH 5/8] test(relayflows): report a resolver timeout as a timeout, not a launch failure Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MB3K5bK7Jc5HM92fsZRUyS --- .../cases/1649-fresh-install-vitest-peer/run.mjs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs b/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs index c8b5a435b..a58a4b1d6 100644 --- a/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs +++ b/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs @@ -84,7 +84,15 @@ try { timeout: COMMAND_TIMEOUT_MS, } ); - if (install.error) throw new Error(`${NPM_UNDER_TEST} could not start: ${install.error.message}`); + if (install.error) { + // spawnSync reports its own timeout as an error with code ETIMEDOUT; a + // slow registry is an infrastructure failure, not a launch failure. + const cause = + install.error.code === 'ETIMEDOUT' + ? `timed out after ${COMMAND_TIMEOUT_MS}ms` + : `could not start: ${install.error.message}`; + throw new Error(`${NPM_UNDER_TEST} ${cause}`); + } const stderr = install.stderr ?? ''; const stdout = install.stdout ?? ''; console.log(`${NPM_UNDER_TEST} install exited with ${install.status ?? `signal ${install.signal}`}`); From 26ff86d88afed8d580f3f586701cad8a51f29afc Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 16:54:59 +0000 Subject: [PATCH 6/8] test(relayflows): make the fresh-install proof report its resolver and registry The Cloud re-run of this proof saw the base arm resolve cleanly while the same manifests still crash locally against registry.npmjs.org with npm 10.9.2, and nothing new was published in between. The case now records which npm actually ran, which registry it talked to, and the latest vitest / vite / @vitejs/devtools* it was served, refuses to run if the resolver is not npm 10.9.2, and fails with an explicit precondition error when that registry cannot serve vitest 5 at all (a stale mirror cannot produce the red arm, and must not read as green). A successful install also logs the resolved vite/devtools chain. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MB3K5bK7Jc5HM92fsZRUyS --- .../1649-fresh-install-vitest-peer/run.mjs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs b/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs index a58a4b1d6..615c19d55 100644 --- a/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs +++ b/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs @@ -60,6 +60,28 @@ try { ); } + // Preflight: pin down what the resolver under test actually is and what the + // registry it talks to serves. The bug only reproduces when the wildcard + // peer can land on vitest 5, so a registry (or caching proxy) that still + // serves vitest 4 as latest cannot produce the base arm's red outcome; say + // so explicitly instead of letting that read as "fixed". + const npmVersion = npmUnderTest(['--version'], probeDir).trim(); + if (npmVersion !== NPM_UNDER_TEST.split('@')[1]) { + throw new Error(`Expected ${NPM_UNDER_TEST} to run, but npx executed npm ${npmVersion}.`); + } + const registry = npmUnderTest(['config', 'get', 'registry'], probeDir).trim(); + const latest = {}; + for (const name of ['vitest', 'vite', '@vitejs/devtools', '@vitejs/devtools-vitest']) { + latest[name] = npmUnderTest(['view', `${name}@latest`, 'version'], probeDir).trim(); + } + console.log(`Resolver: npm ${npmVersion}; registry ${registry}; latest ${JSON.stringify(latest)}`); + const vitestLatestMajor = Number.parseInt(latest.vitest, 10); + if (!Number.isInteger(vitestLatestMajor) || vitestLatestMajor < 5) { + throw new Error( + `Precondition not met: the registry serves vitest ${latest.vitest} as latest, so the wildcard peer cannot resolve to vitest 5 and neither arm can be observed here.` + ); + } + const install = spawnSync( 'npx', [ @@ -108,6 +130,12 @@ try { } else if (install.status === 0) { const lock = JSON.parse(await readFile(path.join(probeDir, 'package-lock.json'), 'utf8')); const resolved = lock.packages?.['node_modules/vitest']?.version; + const chain = Object.fromEntries( + ['node_modules/vite', 'node_modules/@vitejs/devtools', 'node_modules/@vitejs/devtools-vitest'].map( + (key) => [key, lock.packages?.[key]?.version ?? null] + ) + ); + console.log(`Resolved vitest ${resolved}; chain ${JSON.stringify(chain)}`); if (typeof resolved !== 'string' || !resolved.startsWith('4.')) { throw new Error( `Install succeeded but resolved vitest ${JSON.stringify(resolved)}; expected a 4.x matching ${rootRange}.` @@ -158,6 +186,24 @@ async function copyWorkspaceManifests(sourceRoot, destinationRoot) { return count; } +function npmUnderTest(args, cwd) { + const completed = spawnSync('npx', ['--yes', '--package', NPM_UNDER_TEST, '--', 'npm', ...args], { + cwd, + env: { ...process.env, npm_config_update_notifier: 'false' }, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + timeout: 3 * 60 * 1000, + }); + if (completed.error) + throw new Error(`${NPM_UNDER_TEST} ${args[0]} could not run: ${completed.error.message}`); + if (completed.status !== 0) { + throw new Error( + `${NPM_UNDER_TEST} ${args.join(' ')} exited with ${completed.status}: ${(completed.stderr || '').trim().split('\n').slice(-3).join(' | ')}` + ); + } + return completed.stdout; +} + function requiredValue(name) { const value = process.env[name]?.trim(); if (!value) throw new Error(`Missing required environment variable ${name}.`); From b7a17b5ae5eafe7500ede7e5306ab967bd34ca6f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 17:04:37 +0000 Subject: [PATCH 7/8] test(relayflows): keep the vitest lookup as the only gating preflight Only the vitest@latest lookup decides the precondition; the vite and @vitejs/devtools* lookups are context for the log and now degrade to "unavailable" instead of failing the case. A preflight that exceeds its timeout now reads as a timeout, matching the install step. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MB3K5bK7Jc5HM92fsZRUyS --- .../1649-fresh-install-vitest-peer/run.mjs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs b/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs index 615c19d55..579d77191 100644 --- a/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs +++ b/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs @@ -22,6 +22,7 @@ const CASE_ID = '1649-fresh-install-vitest-peer'; const NPM_UNDER_TEST = 'npm@10.9.2'; const CRASH_MARKER = "Cannot read properties of null (reading 'edgesOut')"; const COMMAND_TIMEOUT_MS = 10 * 60 * 1000; +const PREFLIGHT_TIMEOUT_MS = 3 * 60 * 1000; const targetDir = requiredDirectory('RELAY_PR_PROOF_TARGET_DIR'); const harnessDir = requiredDirectory('RELAY_PR_PROOF_HARNESS_DIR'); @@ -70,9 +71,16 @@ try { throw new Error(`Expected ${NPM_UNDER_TEST} to run, but npx executed npm ${npmVersion}.`); } const registry = npmUnderTest(['config', 'get', 'registry'], probeDir).trim(); - const latest = {}; - for (const name of ['vitest', 'vite', '@vitejs/devtools', '@vitejs/devtools-vitest']) { - latest[name] = npmUnderTest(['view', `${name}@latest`, 'version'], probeDir).trim(); + // Only the vitest lookup gates the case; the other three are best-effort + // context for the log and must not turn a registry hiccup into an infra + // failure when the real precondition holds. + const latest = { vitest: npmUnderTest(['view', 'vitest@latest', 'version'], probeDir).trim() }; + for (const name of ['vite', '@vitejs/devtools', '@vitejs/devtools-vitest']) { + try { + latest[name] = npmUnderTest(['view', `${name}@latest`, 'version'], probeDir).trim(); + } catch (error) { + latest[name] = `unavailable (${error instanceof Error ? error.message : String(error)})`; + } } console.log(`Resolver: npm ${npmVersion}; registry ${registry}; latest ${JSON.stringify(latest)}`); const vitestLatestMajor = Number.parseInt(latest.vitest, 10); @@ -192,10 +200,15 @@ function npmUnderTest(args, cwd) { env: { ...process.env, npm_config_update_notifier: 'false' }, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], - timeout: 3 * 60 * 1000, + timeout: PREFLIGHT_TIMEOUT_MS, }); - if (completed.error) - throw new Error(`${NPM_UNDER_TEST} ${args[0]} could not run: ${completed.error.message}`); + if (completed.error) { + const cause = + completed.error.code === 'ETIMEDOUT' + ? `timed out after ${PREFLIGHT_TIMEOUT_MS}ms` + : `could not run: ${completed.error.message}`; + throw new Error(`${NPM_UNDER_TEST} ${args[0]} ${cause}`); + } if (completed.status !== 0) { throw new Error( `${NPM_UNDER_TEST} ${args.join(' ')} exited with ${completed.status}: ${(completed.stderr || '').trim().split('\n').slice(-3).join(' | ')}` From 9e981bc1a04e85ac0a031bf804b140b3629f1ba8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 17:10:56 +0000 Subject: [PATCH 8/8] test(relayflows): bound the proof preflight inside the case timeout Required lookups get 60s each and the optional context lookups 20s each, so the worst-case preflight is four minutes and the 10-minute install still fits the 900s case budget. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MB3K5bK7Jc5HM92fsZRUyS --- .../1649-fresh-install-vitest-peer/run.mjs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs b/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs index 579d77191..a5508f48a 100644 --- a/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs +++ b/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs @@ -22,7 +22,11 @@ const CASE_ID = '1649-fresh-install-vitest-peer'; const NPM_UNDER_TEST = 'npm@10.9.2'; const CRASH_MARKER = "Cannot read properties of null (reading 'edgesOut')"; const COMMAND_TIMEOUT_MS = 10 * 60 * 1000; -const PREFLIGHT_TIMEOUT_MS = 3 * 60 * 1000; +// Preflight budget: three required lookups at most 60s each plus three optional +// ones at 20s each is 4 minutes worst case, which with the 10-minute install +// stays inside the 900s case timeout in case.json. +const PREFLIGHT_TIMEOUT_MS = 60 * 1000; +const OPTIONAL_LOOKUP_TIMEOUT_MS = 20 * 1000; const targetDir = requiredDirectory('RELAY_PR_PROOF_TARGET_DIR'); const harnessDir = requiredDirectory('RELAY_PR_PROOF_HARNESS_DIR'); @@ -77,7 +81,11 @@ try { const latest = { vitest: npmUnderTest(['view', 'vitest@latest', 'version'], probeDir).trim() }; for (const name of ['vite', '@vitejs/devtools', '@vitejs/devtools-vitest']) { try { - latest[name] = npmUnderTest(['view', `${name}@latest`, 'version'], probeDir).trim(); + latest[name] = npmUnderTest( + ['view', `${name}@latest`, 'version'], + probeDir, + OPTIONAL_LOOKUP_TIMEOUT_MS + ).trim(); } catch (error) { latest[name] = `unavailable (${error instanceof Error ? error.message : String(error)})`; } @@ -194,18 +202,18 @@ async function copyWorkspaceManifests(sourceRoot, destinationRoot) { return count; } -function npmUnderTest(args, cwd) { +function npmUnderTest(args, cwd, timeoutMs = PREFLIGHT_TIMEOUT_MS) { const completed = spawnSync('npx', ['--yes', '--package', NPM_UNDER_TEST, '--', 'npm', ...args], { cwd, env: { ...process.env, npm_config_update_notifier: 'false' }, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], - timeout: PREFLIGHT_TIMEOUT_MS, + timeout: timeoutMs, }); if (completed.error) { const cause = completed.error.code === 'ETIMEDOUT' - ? `timed out after ${PREFLIGHT_TIMEOUT_MS}ms` + ? `timed out after ${timeoutMs}ms` : `could not run: ${completed.error.message}`; throw new Error(`${NPM_UNDER_TEST} ${args[0]} ${cause}`); }