diff --git a/src/lib/arg-parsing.ts b/src/lib/arg-parsing.ts index 287ed95ba..588de7738 100644 --- a/src/lib/arg-parsing.ts +++ b/src/lib/arg-parsing.ts @@ -955,10 +955,11 @@ function parseWithHash(arg: string): ParsedIssueArg { return parseBareIssueIdentifier(fragment); } - // `org/project#SHORTID` → equivalent to `org/project/SHORTID`. Reconstruct the - // slash form so parseWithSlash/parseMultiSlashIssueArg handle org/project - // validation and the short-ID prefix-match logic. + // `org/project#SHORTID` → equivalent to `org/project/SHORTID`. Validate the + // org/project prefix components first — the `#` path skips the main + // validateResourceId guard, and parseWithSlash doesn't re-validate. if (prefix.includes("/")) { + validateResourceId(prefix.replace(/\//g, ""), "issue identifier"); return parseWithSlash(`${prefix}/${fragment}`); } diff --git a/test/lib/arg-parsing.test.ts b/test/lib/arg-parsing.test.ts index e07f25334..2fc032976 100644 --- a/test/lib/arg-parsing.test.ts +++ b/test/lib/arg-parsing.test.ts @@ -1356,6 +1356,11 @@ describe("parseIssueArg: GitHub-style # separator (CLI-1G1)", () => { expect(() => parseIssueArg("bad%proj#G")).toThrow(ValidationError); }); + test("forbidden characters in org/project prefix throw", () => { + expect(() => parseIssueArg("bad%org/project#G")).toThrow(ValidationError); + expect(() => parseIssueArg("org/bad%proj#G")).toThrow(ValidationError); + }); + test("multiple # error message suggests the correct format", () => { expect(() => parseIssueArg("a#b#c")).toThrow(/org\/project#PROJ-123/); });