From a6c963345ab23e0726346a9d130c1f8880745a7c Mon Sep 17 00:00:00 2001 From: JSap0914 Date: Sun, 14 Jun 2026 12:36:37 +0900 Subject: [PATCH] fix(opencode): accept leading # in `opencode pr ` `opencode pr #992` printed "Fetching and checking out PR #NaN" and failed because the `number` positional is typed `number`, so yargs coerces `#992` to NaN. `gh pr checkout` accepts a leading `#`, so opencode should too. Type the positional as a string and parse it with `parsePrNumber`, which strips one optional leading `#`, accepts a positive integer, and fails with a clear message otherwise. --- packages/opencode/src/cli/cmd/pr.ts | 14 ++++++++++-- packages/opencode/test/cli/cmd/pr.test.ts | 28 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 packages/opencode/test/cli/cmd/pr.test.ts diff --git a/packages/opencode/src/cli/cmd/pr.ts b/packages/opencode/src/cli/cmd/pr.ts index 420972235746..8995cddf14cb 100644 --- a/packages/opencode/src/cli/cmd/pr.ts +++ b/packages/opencode/src/cli/cmd/pr.ts @@ -5,12 +5,19 @@ import { Git } from "@/git" import { InstanceRef } from "@/effect/instance-ref" import { Process } from "@/util/process" +export function parsePrNumber(input: string | number): number | undefined { + const normalized = String(input).trim().replace(/^#/, "") + if (!/^\d+$/.test(normalized)) return undefined + const value = Number(normalized) + return Number.isInteger(value) && value > 0 ? value : undefined +} + export const PrCommand = effectCmd({ command: "pr ", describe: "fetch and checkout a GitHub PR branch, then run opencode", builder: (yargs) => yargs.positional("number", { - type: "number", + type: "string", describe: "PR number to checkout", demandOption: true, }), @@ -24,7 +31,10 @@ export const PrCommand = effectCmd({ const git = yield* Git.Service const worktree = ctx.worktree - const prNumber = args.number + const prNumber = parsePrNumber(args.number) + if (prNumber === undefined) { + return yield* fail(`Invalid PR number: ${args.number}. Pass a positive integer, optionally prefixed with '#'.`) + } const localBranchName = `pr/${prNumber}` UI.println(`Fetching and checking out PR #${prNumber}...`) diff --git a/packages/opencode/test/cli/cmd/pr.test.ts b/packages/opencode/test/cli/cmd/pr.test.ts new file mode 100644 index 000000000000..9b619d9287d4 --- /dev/null +++ b/packages/opencode/test/cli/cmd/pr.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, test } from "bun:test" +import { parsePrNumber } from "@/cli/cmd/pr" + +describe("cli.pr parsePrNumber", () => { + test("accepts a bare number", () => { + expect(parsePrNumber("992")).toBe(992) + expect(parsePrNumber(992)).toBe(992) + }) + + test("accepts a leading # (issue #32251)", () => { + expect(parsePrNumber("#992")).toBe(992) + }) + + test("trims surrounding whitespace", () => { + expect(parsePrNumber(" #992 ")).toBe(992) + }) + + test("rejects non-numeric, empty, zero, negative, decimal, and double-hash input", () => { + expect(parsePrNumber("#")).toBeUndefined() + expect(parsePrNumber("")).toBeUndefined() + expect(parsePrNumber("abc")).toBeUndefined() + expect(parsePrNumber("0")).toBeUndefined() + expect(parsePrNumber("-3")).toBeUndefined() + expect(parsePrNumber("12.5")).toBeUndefined() + expect(parsePrNumber("99x")).toBeUndefined() + expect(parsePrNumber("##992")).toBeUndefined() + }) +})