From 6b29db73bed2e47a46e91bdc4b4c87b9d97c7b66 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Wed, 19 Aug 2026 16:08:47 +0200 Subject: [PATCH] fix: create the cache dir before symlinking the re-warm project at it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The re-warm broke the weekly run on its first outing, on pnpm 11: the cache directory is deleted two rows before update, and a manager whose `node_modules` row is a registry-free no-op — pnpm restores the tree from the lockfile copy inside it — never recreates it. The re-warm's `cache` symlink then points at nothing, and the warm-up install dies with ENOTDIR trying to mkdir its store through a dangling link. The smoke test missed it because pnpm 12 reaches the cache through absolute environment paths rather than the symlink, and Bun's re-downloading `node_modules` row recreates the directory — pnpm 11 is the manager that goes through the symlink AND skips the re-download. The directory is now created (idempotently) before the symlink, and the pnpm 11 chain runs clean end to end: its update lands at 1.8s against the 7.5s the cold-cache artifact used to charge it. Also: when every measuring job fails, the report job's samples directory never comes into existence, and merge-results died with a raw ENOENT scandir instead of its own explanation. A missing directory is the same situation as an empty one and now gets the same error. Co-Authored-By: Claude Opus 5 (1M context) --- benchmarkFixture.js | 6 ++++++ mergeResults.js | 13 ++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/benchmarkFixture.js b/benchmarkFixture.js index 7100b5f..dc85c31 100644 --- a/benchmarkFixture.js +++ b/benchmarkFixture.js @@ -406,6 +406,12 @@ export default async function benchmark (pm, fixture, opts) { if (err?.code !== 'ENOENT') throw err } } + // The cache dir may not exist right now: it was deleted two rows up, and + // a manager whose `node_modules` row is a registry-free no-op (pnpm + // restores the tree from the lockfile copy inside it) never recreated + // it. A symlink to a path that isn't there is dangling, and the warm-up + // install fails trying to mkdir its store through it. + await fs.mkdir(path.join(cwd, 'cache'), { recursive: true }) await fs.symlink(path.join(cwd, 'cache'), path.join(rewarmDir, 'cache'), 'dir') measureInstall(pm, rewarmDir, env) } finally { diff --git a/mergeResults.js b/mergeResults.js index b311b36..a40be88 100644 --- a/mergeResults.js +++ b/mergeResults.js @@ -47,7 +47,18 @@ async function main () { if (!samplesDir) { throw new Error('Usage: node mergeResults.js ') } - const runDirs = fs.readdirSync(samplesDir, { withFileTypes: true }) + // A samples directory that doesn't exist means no measuring job uploaded + // anything — every one of them failed. That is the same situation as an + // empty directory, and it deserves the same explanation rather than a raw + // ENOENT from `readdirSync`. + let entries + try { + entries = fs.readdirSync(samplesDir, { withFileTypes: true }) + } catch (err) { + if (err.code !== 'ENOENT') throw err + entries = [] + } + const runDirs = entries .filter((entry) => entry.isDirectory()) // Sorted so that a merge of the same runs always produces the same file, // whatever order the artifacts happened to be downloaded in.