From 207dec19a309d0a9351e76d94e35d918140bd9a7 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Fri, 3 Jul 2026 02:40:15 +0800 Subject: [PATCH 1/2] fix(eval): resolve tsx runtime from ancestor node_modules in worktrees Fresh git worktrees have no node_modules of their own, so the hard-coded resolve(projectRoot, "node_modules", "tsx", "dist", "cli.mjs") path failed with "Could not find tsx runtime", breaking every `npm run eval:*` command in a worktree. Walk up ancestor directories (like Node's own module resolution) so the main checkout's install is found. A plain existsSync walk is used instead of require.resolve because tsx's package "exports" map does not expose dist/cli.mjs. Co-Authored-By: Claude Fable 5 --- scripts/run-eval-safe.mjs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/scripts/run-eval-safe.mjs b/scripts/run-eval-safe.mjs index 0d13fe29c..0a5718d68 100644 --- a/scripts/run-eval-safe.mjs +++ b/scripts/run-eval-safe.mjs @@ -206,11 +206,28 @@ function terminateEvalProcess(pid) { terminateEvalProcessTree(pid); } +function resolveTsxCli() { + // Fresh git worktrees often have no node_modules of their own, so walk up + // the ancestor directories like Node's own module resolution does: the repo + // root's install wins when present, otherwise the main checkout's install is + // found (worktrees live inside the repo directory). A plain existsSync walk + // is used instead of require.resolve because tsx's package "exports" map + // does not expose dist/cli.mjs. + let dir = projectRoot; + for (;;) { + const candidate = resolve(dir, "node_modules", "tsx", "dist", "cli.mjs"); + if (existsSync(candidate)) return candidate; + const parent = dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + function runEvalScript() { - const tsxBin = resolve(projectRoot, "node_modules", "tsx", "dist", "cli.mjs"); + const tsxBin = resolveTsxCli(); - if (!existsSync(tsxBin)) { - console.error(`Could not find tsx runtime at ${tsxBin}`); + if (!tsxBin) { + console.error("Could not resolve the tsx runtime. Run `npm install` first (or invoke the script with `npx tsx` directly)."); process.exit(1); } From 557a45d3030645915908ca96b1e3b6ee84ec48d6 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Fri, 3 Jul 2026 08:28:46 +0800 Subject: [PATCH 2/2] fix(eval): resolve tsx from sibling worktrees --- scripts/run-eval-safe.mjs | 46 +++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/scripts/run-eval-safe.mjs b/scripts/run-eval-safe.mjs index 0a5718d68..48fcb1944 100644 --- a/scripts/run-eval-safe.mjs +++ b/scripts/run-eval-safe.mjs @@ -206,14 +206,8 @@ function terminateEvalProcess(pid) { terminateEvalProcessTree(pid); } -function resolveTsxCli() { - // Fresh git worktrees often have no node_modules of their own, so walk up - // the ancestor directories like Node's own module resolution does: the repo - // root's install wins when present, otherwise the main checkout's install is - // found (worktrees live inside the repo directory). A plain existsSync walk - // is used instead of require.resolve because tsx's package "exports" map - // does not expose dist/cli.mjs. - let dir = projectRoot; +function resolveFromAncestorNodeModules(startDir) { + let dir = startDir; for (;;) { const candidate = resolve(dir, "node_modules", "tsx", "dist", "cli.mjs"); if (existsSync(candidate)) return candidate; @@ -223,11 +217,45 @@ function resolveTsxCli() { } } +function listGitWorktreeRoots() { + const result = spawnSync("git", ["worktree", "list", "--porcelain"], { + cwd: projectRoot, + encoding: "utf8", + windowsHide: true, + }); + if (result.status !== 0) return []; + + return (result.stdout || "") + .split(/\r?\n/) + .filter((line) => line.startsWith("worktree ")) + .map((line) => line.slice("worktree ".length).trim()) + .filter(Boolean); +} + +function resolveTsxCli() { + // Fresh git worktrees often have no node_modules of their own. First walk up + // ancestor directories like Node's own module resolution; then inspect Git's + // known worktrees so sibling/external worktrees can reuse the main checkout's + // install. A plain existsSync probe is used because tsx's package "exports" + // map does not expose dist/cli.mjs. + const ancestorMatch = resolveFromAncestorNodeModules(projectRoot); + if (ancestorMatch) return ancestorMatch; + + for (const root of listGitWorktreeRoots()) { + const worktreeMatch = resolveFromAncestorNodeModules(root); + if (worktreeMatch) return worktreeMatch; + } + + return null; +} + function runEvalScript() { const tsxBin = resolveTsxCli(); if (!tsxBin) { - console.error("Could not resolve the tsx runtime. Run `npm install` first (or invoke the script with `npx tsx` directly)."); + console.error( + "Could not resolve the tsx runtime. Run `npm install` first (or invoke the script with `npx tsx` directly).", + ); process.exit(1); }