Skip to content

Commit ebbf21b

Browse files
committed
fix: handle full short IDs and numeric IDs in multi-slash issue args (CLI-KC, CLI-B6)
When users pass `org/project/FULL-SHORT-ID` (e.g., `sentry/cli/CLI-A1`), parseMultiSlashIssueArg would split the short ID on its last dash and recombine it as a suffix. This caused expandToFullShortId to produce a double-prefixed ID like `CLI-CLI-A1`. Similarly, passing `org/project/NUMERIC-ID` (e.g., `fever/cashless/6918259357`) treated the numeric ID as a suffix, producing nonsensical short IDs like `CASHLESS-6918259357`. Fix: - Detect pure numeric remainders and return explicit-org-numeric type - When the remainder is a full short ID whose prefix matches the project slug, extract just the suffix (strip the redundant project prefix) Fixes CLI-KC (33 events, escalating), CLI-B6 (3 events, 2 users).
1 parent a03a9d1 commit ebbf21b

File tree

2 files changed

+85
-14
lines changed

2 files changed

+85
-14
lines changed

src/lib/arg-parsing.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,11 @@ export type ParsedIssueArg =
607607
*
608608
* Splits `rest` on its first `/` to extract the project slug and a remainder
609609
* that is treated as the issue reference (suffix, numeric ID, or short ID).
610+
*
611+
* Handles three remainder formats:
612+
* - Pure digits → numeric issue ID (project context is redundant)
613+
* - Full short ID whose prefix matches the project → extract just the suffix
614+
* - Anything else → treat entire remainder as the suffix
610615
*/
611616
function parseMultiSlashIssueArg(
612617
arg: string,
@@ -626,22 +631,44 @@ function parseMultiSlashIssueArg(
626631
// Lowercase project slug — Sentry slugs are always lowercase.
627632
const normalizedProject = project.toLowerCase();
628633

629-
// Remainder with dash: "org/project/PROJ-G" — split remainder on last dash
634+
// Pure numeric remainder: "org/project/123456789" → org + numeric ID.
635+
// Project context is redundant for numeric IDs — Sentry resolves them globally.
636+
if (isAllDigits(remainder)) {
637+
return { type: "explicit-org-numeric", org, numericId: remainder };
638+
}
639+
640+
// Remainder with dash: could be a full short ID like "CLI-A1" or "SPOTLIGHT-ELECTRON-4Y"
630641
if (remainder.includes("-")) {
631642
const lastDash = remainder.lastIndexOf("-");
632-
const subProject = remainder.slice(0, lastDash);
643+
const prefix = remainder.slice(0, lastDash);
633644
const suffix = remainder.slice(lastDash + 1).toUpperCase();
634-
if (subProject && suffix) {
645+
646+
if (prefix && suffix) {
647+
// Check if the prefix matches the project slug (case-insensitive).
648+
// If so, the remainder is already a full short ID — use only the suffix.
649+
// e.g., "sentry/cli/CLI-A1" → prefix "CLI" matches project "cli" → suffix "A1"
650+
// e.g., "org/spotlight-electron/SPOTLIGHT-ELECTRON-4Y" → prefix matches → suffix "4Y"
651+
if (prefix.toLowerCase() === normalizedProject) {
652+
return {
653+
type: "explicit",
654+
org,
655+
project: normalizedProject,
656+
suffix,
657+
};
658+
}
659+
660+
// Prefix doesn't match project — treat entire remainder as the suffix.
661+
// e.g., "org/project/SUBPROJ-G" where SUBPROJ ≠ project
635662
return {
636663
type: "explicit",
637664
org,
638665
project: normalizedProject,
639-
suffix: `${subProject}-${suffix}`.toUpperCase(),
666+
suffix: `${prefix}-${suffix}`.toUpperCase(),
640667
};
641668
}
642669
}
643670

644-
// "org/project/101149101" or "org/project/G" — treat remainder as suffix
671+
// No dash: "org/project/G" — treat remainder as suffix
645672
return {
646673
type: "explicit",
647674
org,

test/lib/arg-parsing.test.ts

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -464,25 +464,23 @@ describe("parseIssueArg", () => {
464464

465465
// Multi-slash issue args (org/project/suffix)
466466
describe("multi-slash issue args", () => {
467-
test("org/project/suffix returns explicit with project and suffix", () => {
467+
test("org/project/numeric returns explicit-org-numeric", () => {
468468
expect(parseIssueArg("org/project/101149101")).toEqual({
469-
type: "explicit",
469+
type: "explicit-org-numeric",
470470
org: "org",
471-
project: "project",
472-
suffix: "101149101",
471+
numericId: "101149101",
473472
});
474473
});
475474

476-
test("org/project/numeric returns explicit with numeric suffix", () => {
475+
test("org/project/short-numeric returns explicit-org-numeric", () => {
477476
expect(parseIssueArg("org/project/123456")).toEqual({
478-
type: "explicit",
477+
type: "explicit-org-numeric",
479478
org: "org",
480-
project: "project",
481-
suffix: "123456",
479+
numericId: "123456",
482480
});
483481
});
484482

485-
test("org/project/PROJ-G returns explicit with combined suffix", () => {
483+
test("org/project/PROJ-G where PROJ ≠ project returns explicit with combined suffix", () => {
486484
expect(parseIssueArg("org/project/PROJ-G")).toEqual({
487485
type: "explicit",
488486
org: "org",
@@ -491,6 +489,52 @@ describe("parseIssueArg", () => {
491489
});
492490
});
493491

492+
test("org/project/PROJECT-G where prefix matches project strips prefix (CLI-KC)", () => {
493+
expect(parseIssueArg("sentry/cli/CLI-A1")).toEqual({
494+
type: "explicit",
495+
org: "sentry",
496+
project: "cli",
497+
suffix: "A1",
498+
});
499+
});
500+
501+
test("org/project/PROJECT-suffix is case-insensitive on prefix match", () => {
502+
expect(parseIssueArg("sentry/cli/cli-b6")).toEqual({
503+
type: "explicit",
504+
org: "sentry",
505+
project: "cli",
506+
suffix: "B6",
507+
});
508+
});
509+
510+
test("compound project slug with matching full short ID (CLI-KC)", () => {
511+
expect(
512+
parseIssueArg("org/spotlight-electron/SPOTLIGHT-ELECTRON-4Y")
513+
).toEqual({
514+
type: "explicit",
515+
org: "org",
516+
project: "spotlight-electron",
517+
suffix: "4Y",
518+
});
519+
});
520+
521+
test("org/project/numeric-id returns explicit-org-numeric (CLI-B6)", () => {
522+
expect(parseIssueArg("fever/cashless/6918259357")).toEqual({
523+
type: "explicit-org-numeric",
524+
org: "fever",
525+
numericId: "6918259357",
526+
});
527+
});
528+
529+
test("org/project/G returns explicit with suffix", () => {
530+
expect(parseIssueArg("org/project/G")).toEqual({
531+
type: "explicit",
532+
org: "org",
533+
project: "project",
534+
suffix: "G",
535+
});
536+
});
537+
494538
test("org/project/ (trailing slash, empty suffix) throws error", () => {
495539
expect(() => parseIssueArg("org/project/")).toThrow(
496540
"Missing project or issue ID segment"

0 commit comments

Comments
 (0)