From a7e393bc53375f0ecbb58b9683f1eb1697bc8fb1 Mon Sep 17 00:00:00 2001 From: yexisu <1761512688@qq.com> Date: Sun, 4 Oct 2026 10:29:29 +0800 Subject: [PATCH] fix(dev): Windows-proof the Pi patch checks and fork test paths check:pi-dependencies and check:pi-patches detected pnpm's patched instance by matching the literal `patch_hash=` segment in realpath output. Windows shortens `.pnpm` store directory names (long-path limit), so the segment disappears and both checks misreport a healthy install (#1361). The patch hash is now proven through the lockfiles: the root pnpm-lock.yaml patchedDependencies entry declares the 64-hex hash and the virtual-store lockfile must embed it in the installed snapshot's `version: (patch_hash=...)` line, which survives the shortened directories. The installed manifest is still version-checked directly. The native fork fixture compared `foreignPath.split("/").at(-1)` with readdirSync output; on Windows the split never breaks the path, so the assertion compared an absolute path against a file name. Use path.basename instead. Validated: the fork test now passes on Windows (it failed on pristine main); the two-stage hash chain verified against this machine's 0.99.1 lockfiles (declared hash resolves, stale virtual store correctly fails the embed check). --- .../src/native-pi-session.test.ts | 4 ++-- scripts/check-pi-dependencies.mjs | 21 +++++++++++++++- scripts/check-pi-patches.mjs | 24 +++++++++++++++++-- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/packages/agent-runtime/src/native-pi-session.test.ts b/packages/agent-runtime/src/native-pi-session.test.ts index d9f4b34366..e7cec08e3a 100644 --- a/packages/agent-runtime/src/native-pi-session.test.ts +++ b/packages/agent-runtime/src/native-pi-session.test.ts @@ -1,6 +1,6 @@ import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { basename, join } from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { AgentSession, ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent"; import { createAssistantMessageEventStream, type AssistantMessage } from "@earendil-works/pi-ai"; @@ -781,7 +781,7 @@ describe("native fork children", () => { expect(failure?.message).not.toContain(f.group); expect(existsSync(foreignPath)).toBe(true); expect(readFileSync(foreignPath, "utf8")).toContain("collision"); - expect(groupEntries(f.group).sort()).toEqual(before.concat([foreignPath.split("/").at(-1)!]).sort()); + expect(groupEntries(f.group).sort()).toEqual(before.concat([basename(foreignPath)]).sort()); expect(readFileSync(f.file, "utf8")).toBe(parentBytes); } finally { service.disposeAll(); } } finally { diff --git a/scripts/check-pi-dependencies.mjs b/scripts/check-pi-dependencies.mjs index c31cc6acf5..6806d8c870 100644 --- a/scripts/check-pi-dependencies.mjs +++ b/scripts/check-pi-dependencies.mjs @@ -4,6 +4,25 @@ import { fileURLToPath } from "node:url"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const targetVersion = "1.0.1"; +// Proof chain that pnpm installed the patched instance: the root lockfile's +// patchedDependencies section maps `name@version` to a 64-hex patch hash, and +// the virtual-store lockfile embeds that hash in the installed snapshot's +// `version: (patch_hash=)` line. realpath-based matching cannot work +// on Windows, where pnpm shortens `.pnpm` directory names (long-path limit) +// and the literal `patch_hash=` segment disappears from resolved paths +// (#1361). +const rootLockfile = readFileSync(join(root, "pnpm-lock.yaml"), "utf8"); +const virtualStoreLockfile = readFileSync(join(root, "node_modules/.pnpm/lock.yaml"), "utf8"); + +function installedPatchHash(packageName) { + const declared = rootLockfile.match( + new RegExp(`'?${packageName.replace("/", "/")}@${targetVersion}'?:[ \\t]*([a-f0-9]{64})`), + )?.[1]; + if (!declared) return undefined; + return virtualStoreLockfile.includes(`(patch_hash=${declared}`) + ? declared + : undefined; +} function readJson(path) { return JSON.parse(readFileSync(join(root, path), "utf8")); @@ -25,7 +44,7 @@ function assertInstalled(packagePath, expectedName, { patched = false } = {}) { if (manifest.name !== expectedName || manifest.version !== targetVersion) { throw new Error(`${packagePath} resolves to ${manifest.name}@${manifest.version}, expected ${expectedName}@${targetVersion}`); } - if (patched && !resolved.includes("patch_hash=")) { + if (patched && !installedPatchHash(expectedName)) { throw new Error(`${packagePath} does not resolve to pnpm's patched package instance`); } } diff --git a/scripts/check-pi-patches.mjs b/scripts/check-pi-patches.mjs index 2164f4f812..367df9bcfc 100644 --- a/scripts/check-pi-patches.mjs +++ b/scripts/check-pi-patches.mjs @@ -26,6 +26,14 @@ const entries = [ ]; const workspace = readFileSync(join(root, "pnpm-workspace.yaml"), "utf8"); const lockfile = readFileSync(join(root, "pnpm-lock.yaml"), "utf8"); +// Proof chain that pnpm installed the patched instance: the root lockfile's +// patchedDependencies section maps `name@version` to a 64-hex patch hash, and +// the virtual-store lockfile embeds that hash in the installed snapshot's +// `version: (patch_hash=)` line. realpath-based matching cannot work +// on Windows, where pnpm shortens `.pnpm` directory names (long-path limit) +// and the literal `patch_hash=` segment disappears from resolved paths +// (#1361). +const virtualStoreLockfile = readFileSync(join(root, "node_modules/.pnpm/lock.yaml"), "utf8"); for (const entry of entries) { if (!existsSync(join(root, entry.patch))) throw new Error(`Missing patch: ${entry.patch}`); @@ -40,8 +48,20 @@ for (const entry of entries) { if (!workspace.includes(workspaceMapping)) throw new Error(`pnpm-workspace.yaml does not map ${entry.name} to ${entry.patch}`); const packagePath = join(root, entry.packagePath); const resolved = realpathSync(packagePath); - const patchHash = resolved.match(/patch_hash=([a-f0-9]+)/)?.[1]; - if (!patchHash || !lockfile.includes(`${entry.name}@${targetVersion}(patch_hash=${patchHash}`)) { + // The installed manifest pins the exact patched version; the hash itself + // comes from the root lockfile's patchedDependencies entry and must be + // embedded in the installed virtual-store snapshot (#1361). + const installedVersion = JSON.parse(readFileSync(join(resolved, "package.json"), "utf8")).version; + if (installedVersion !== targetVersion) { + throw new Error(`${entry.packagePath} resolves to ${entry.name}@${installedVersion}, expected ${targetVersion}`); + } + const declaredHash = lockfile.match( + new RegExp(`'?${entry.name}@${targetVersion}'?:[ \\t]*([a-f0-9]{64})`), + )?.[1]; + if (!declaredHash) { + throw new Error(`pnpm-lock.yaml has no patchedDependencies hash for ${entry.name}@${targetVersion}`); + } + if (!virtualStoreLockfile.includes(`(patch_hash=${declaredHash}`)) { throw new Error(`${entry.name}@${targetVersion} installed patch hash is absent from pnpm-lock.yaml`); } }