From e15435b651a36ea78a39e4fff2628e05788f4855 Mon Sep 17 00:00:00 2001 From: Phorel <141418349+phorel@users.noreply.github.com> Date: Fri, 16 Oct 2026 01:08:11 +0200 Subject: [PATCH] fix(cli): accept npm 12 array output when resolving published version (#14000) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The CLI `install` and `update` commands resolve the published version with `npm view paperclipai@ version --json` > - npm 12 changed the output of this command. It wraps a single value in an array: `["2026.916.1"]` instead of `"2026.916.1"` > - `parseResolvedVersion()` accepts only a bare JSON string, so it throws "npm returned an unexpected version response" > - Node 26 ships with npm 12, so a new install with the official `install.sh` fails for these users > - This pull request makes the parser accept a one-element string array > - The benefit is that install and update work with npm 12 and with older npm versions ## Linked Issues or Issue Description Fixes #12226 ## What Changed - `cli/src/commands/install.ts`: `parseResolvedVersion()` returns the value when npm returns a one-element array of strings. Other shapes still throw the same error. - `cli/src/__tests__/install-command.test.ts`: add a test for npm 11 output, npm 12 output, and plain-text output. The test also checks that a multi-element array still throws. ## Verification - Before the fix: on Node 26.8.2 with npm 12.0.2, `bash install.sh` fails with `npm returned an unexpected version response: [ "2026.916.1" ]`. - `npm view paperclipai@latest version --json` gives `["2026.916.1"]` with npm 12.0.2 and `"2026.916.1"` with npm 11.20.0. - `cd cli && npx vitest run src/__tests__/install-command.test.ts src/__tests__/update-command.test.ts src/__tests__/channels.test.ts`: 40 tests pass. - Without the source change, the new test fails with the same error as the bug report. ## Risks - Low risk. The change adds one accepted input shape. Existing accepted shapes do not change. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use (shell and file editing). It helped to find the cause, write the change and the test, and run the tests. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- cli/src/__tests__/install-command.test.ts | 10 ++++++++++ cli/src/commands/install.ts | 2 ++ 2 files changed, 12 insertions(+) diff --git a/cli/src/__tests__/install-command.test.ts b/cli/src/__tests__/install-command.test.ts index 620504e348a..9c8c06c1716 100644 --- a/cli/src/__tests__/install-command.test.ts +++ b/cli/src/__tests__/install-command.test.ts @@ -10,6 +10,7 @@ import { resolveGitInstallRequest, resolveGitInstallWorkspacePackages, resolveNpmInstallRequest, + resolvePublishedVersion, runCommandWithDiagnostics, } from "../commands/install.js"; import { uninstallCommand } from "../commands/uninstall.js"; @@ -62,6 +63,15 @@ describe("managed install commands", () => { expect(() => resolveNpmInstallRequest({ version: "latest" })).toThrow(); }); + it("parses npm view version output from npm 11 and npm 12", async () => { + for (const stdout of ['"2026.916.1"\n', '[\n "2026.916.1"\n]\n', "2026.916.1\n"]) { + const runCommand = vi.fn(async () => ({ stdout, stderr: "" })); + await expect(resolvePublishedVersion("latest", runCommand)).resolves.toBe("2026.916.1"); + } + const multiple = vi.fn(async () => ({ stdout: '["2026.916.0","2026.916.1"]', stderr: "" })); + await expect(resolvePublishedVersion("latest", multiple)).rejects.toThrow("unexpected version response"); + }); + it("resolves branch, tag, full SHA, and short SHA refs through GitHub", async () => { const sha = "a".repeat(40); const runCommand = vi.fn(async (_file: string, _args: string[]) => ({ stdout: JSON.stringify({ sha }), stderr: "" })); diff --git a/cli/src/commands/install.ts b/cli/src/commands/install.ts index 3fd4a5b1985..b8083a140f0 100644 --- a/cli/src/commands/install.ts +++ b/cli/src/commands/install.ts @@ -111,6 +111,8 @@ function parseResolvedVersion(stdout: string): string { try { const parsed = JSON.parse(trimmed) as unknown; if (typeof parsed === "string") return parsed; + // npm 12 wraps single-value `npm view --json` output in an array. + if (Array.isArray(parsed) && parsed.length === 1 && typeof parsed[0] === "string") return parsed[0]; } catch { if (EXACT_VERSION_PATTERN.test(trimmed)) return trimmed; }