fix(coding-agent): accept managed install markers with a UTF-8 BOM

Windows PowerShell 5.1 adds a BOM when it rewrites managed-install.json, which made pi update fail with "Managed install marker is missing or invalid".
This commit is contained in:
Armin Ronacher 2026-10-20 10:29:22 +02:00
parent 530015d93a
commit ea25697e91
3 changed files with 21 additions and 1 deletions

View file

@ -14,6 +14,7 @@
- Fixed compaction and branch summaries failing with "generation hit the token cap" when reasoning used most of the fixed summary output limit. Summaries now use the model's output limit, and `compaction.reserveTokens` only controls when compaction starts ([#9512](https://github.com/earendil-works/pi/issues/9512), [#9075](https://github.com/earendil-works/pi/issues/9075)).
- Fixed `models.schema.json` missing descriptions for provider and model fields, so editors showed no hints for them ([#10790](https://github.com/earendil-works/pi/issues/10790))
- Fixed `pi update` failing with "Managed install marker is missing or invalid" when `managed-install.json` starts with a UTF-8 BOM, which Windows PowerShell 5.1 adds when it rewrites the file.
## [1.3.0] - 2026-10-15

View file

@ -37,6 +37,7 @@ import { hasTrustRequiringProjectResources, ProjectTrustStore } from "./core/tru
import { spawnProcess, spawnProcessSync, waitForChildProcess } from "./utils/child-process.ts";
import { canonicalizePath, getCwdRelativePath } from "./utils/paths.ts";
import { getPiUserAgent } from "./utils/pi-user-agent.ts";
import { stripBom } from "./utils/text.ts";
import { formatVersionCheckError, getLatestPiRelease, isNewerPackageVersion } from "./utils/version-check.ts";
import {
cleanupWindowsSelfUpdateQuarantine,
@ -63,7 +64,7 @@ function getActiveManagedInstallRoot(): string | undefined {
const markerPath = join(managedRoot, MANAGED_INSTALL_MARKER);
try {
const marker = JSON.parse(readFileSync(markerPath, "utf8")) as {
const marker = JSON.parse(stripBom(readFileSync(markerPath, "utf8"))) as {
kind?: unknown;
layout?: unknown;
schemaVersion?: unknown;

View file

@ -697,6 +697,24 @@ if (process.platform !== "win32") fs.chmodSync(piPath, 0o755);
expect(process.exitCode).toBeUndefined();
});
it("accepts a managed install marker with a UTF-8 BOM", async () => {
const targetVersion = getNewerPatchVersion();
const { managedRoot } = prepareManagedInstall(targetVersion);
writeFileSync(
join(managedRoot, "managed-install.json"),
`\uFEFF${JSON.stringify({ kind: "pi-managed-install", schemaVersion: 1, layout: "releases-v1" })}\r\n`,
);
mockManagedUpdate(targetVersion);
vi.spyOn(console, "log").mockImplementation(() => {});
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
await expect(runPackageCommandDirectly(["update", "--self"])).resolves.toBeUndefined();
expect(readFileSync(join(managedRoot, "current-version"), "utf8")).toBe(`${targetVersion}\n`);
expect(errorSpy).not.toHaveBeenCalled();
expect(process.exitCode).toBeUndefined();
});
it("removes older managed releases after an update", async () => {
// https://github.com/earendil-works/pi/issues/10392
const targetVersion = getNewerPatchVersion();