From b7c33cb874d7215e4ca46e24a30a7a109b754b5d Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Mon, 23 Mar 2026 14:00:40 +0000 Subject: [PATCH 1/2] fix(event): detect org/ISSUE-SHORT-ID in event view single-arg path (CLI-9K) When users pass `figma/FULLSCREEN-2RN` to `sentry event view`, parseSlashSeparatedArg sees exactly one slash and throws ContextError ("Event ID is required") since it interprets `figma` as org and `FULLSCREEN-2RN` as a project slug with no event ID. PR #524 added short ID detection for bare args like `BRUNCHIE-APP-29`, but the `org/SHORT-ID` pattern was not covered because parseSlashSeparatedArg throws before the detection code runs. Fix: extract single-arg handling into `parseSingleArg()` which checks for the `org/ISSUE-SHORT-ID` pattern before calling parseSlashSeparatedArg. This also reduces parsePositionalArgs complexity from 16 to under the biome limit of 15. Fixes CLI-9K (13 events, 11 users). --- src/commands/event/view.ts | 61 ++++++++++++++++++++++---------- test/commands/event/view.test.ts | 22 ++++++++++++ 2 files changed, 65 insertions(+), 18 deletions(-) diff --git a/src/commands/event/view.ts b/src/commands/event/view.ts index 4d42bb6b..391567c9 100644 --- a/src/commands/event/view.ts +++ b/src/commands/event/view.ts @@ -104,6 +104,48 @@ function jsonTransformEventView( /** Usage hint for ContextError messages */ const USAGE_HINT = "sentry event view / "; +/** + * Parse a single positional arg for event view, handling issue short ID + * detection both in bare form ("BRUNCHIE-APP-29") and org-prefixed form + * ("figma/FULLSCREEN-2RN"). + * + * Must run before `parseSlashSeparatedArg` because that function throws + * ContextError for single-slash args like "org/SHORT-ID", which looks like + * "org/project" with a missing event ID. + */ +function parseSingleArg(arg: string): ParsedPositionalArgs { + // Detect "org/SHORT-ID" pattern before parseSlashSeparatedArg. + // e.g., "figma/FULLSCREEN-2RN" → auto-redirect to that issue's latest event. + const slashIdx = arg.indexOf("/"); + if (slashIdx !== -1 && arg.indexOf("/", slashIdx + 1) === -1) { + const afterSlash = arg.slice(slashIdx + 1); + if (afterSlash && looksLikeIssueShortId(afterSlash)) { + return { + eventId: "latest", + targetArg: arg.slice(0, slashIdx), + issueShortId: afterSlash, + }; + } + } + + const { id: eventId, targetArg } = parseSlashSeparatedArg( + arg, + "Event ID", + USAGE_HINT + ); + + // Detect bare issue short ID passed as event ID (e.g., "BRUNCHIE-APP-29"). + if (!targetArg && looksLikeIssueShortId(eventId)) { + return { + eventId: "latest", + targetArg: undefined, + issueShortId: eventId, + }; + } + + return { eventId, targetArg }; +} + /** Return type for parsePositionalArgs */ type ParsedPositionalArgs = { eventId: string; @@ -175,24 +217,7 @@ export function parsePositionalArgs(args: string[]): ParsedPositionalArgs { } if (args.length === 1) { - const { id: eventId, targetArg } = parseSlashSeparatedArg( - first, - "Event ID", - USAGE_HINT - ); - - // Detect issue short ID passed as event ID (e.g., "BRUNCHIE-APP-29"). - // When a single arg matches the issue short ID pattern, the user likely - // wanted `sentry issue view`. Auto-redirect to show the latest event. - if (!targetArg && looksLikeIssueShortId(eventId)) { - return { - eventId: "latest", - targetArg: undefined, - issueShortId: eventId, - }; - } - - return { eventId, targetArg }; + return parseSingleArg(first); } const second = args[1]; diff --git a/test/commands/event/view.test.ts b/test/commands/event/view.test.ts index dc3949a5..bff29551 100644 --- a/test/commands/event/view.test.ts +++ b/test/commands/event/view.test.ts @@ -81,6 +81,28 @@ describe("parsePositionalArgs", () => { expect(result.eventId).toBe("my-project"); expect(result.issueShortId).toBeUndefined(); }); + + test("detects org/ISSUE-SHORT-ID pattern (CLI-9K)", () => { + const result = parsePositionalArgs(["figma/FULLSCREEN-2RN"]); + expect(result.eventId).toBe("latest"); + expect(result.targetArg).toBe("figma"); + expect(result.issueShortId).toBe("FULLSCREEN-2RN"); + }); + + test("detects org/CLI-G pattern", () => { + const result = parsePositionalArgs(["sentry/CLI-G"]); + expect(result.eventId).toBe("latest"); + expect(result.targetArg).toBe("sentry"); + expect(result.issueShortId).toBe("CLI-G"); + }); + + test("does not detect org/lowercase-slug as issue short ID", () => { + // "my-org/my-project" is a normal org/project target, not an issue short ID. + // parseSlashSeparatedArg will throw ContextError as expected. + expect(() => parsePositionalArgs(["my-org/my-project"])).toThrow( + "Event ID" + ); + }); }); describe("two arguments (target + event ID)", () => { From be288483d4b367942b2ac39e32f115958a944231 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Mon, 23 Mar 2026 14:08:18 +0000 Subject: [PATCH 2/2] fix: pass explicit org from org/SHORT-ID pattern to resolveIssueShortcut Address Seer and Cursor Bugbot review: parseSingleArg returned targetArg without trailing slash ("figma" instead of "figma/"), causing parseOrgProjectArg to interpret it as a project search instead of an org. resolveIssueShortcut also ignored the parsed org entirely for the issueShortId branch. Fix: - Add trailing slash to targetArg ("figma/") to signal OrgAll mode - Use the explicit org from parsed target in resolveIssueShortcut when available, falling back to auto-detection only when no org was given --- src/commands/event/view.ts | 14 ++++++++++---- test/commands/event/view.test.ts | 5 +++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/commands/event/view.ts b/src/commands/event/view.ts index 391567c9..8f70fca9 100644 --- a/src/commands/event/view.ts +++ b/src/commands/event/view.ts @@ -120,9 +120,11 @@ function parseSingleArg(arg: string): ParsedPositionalArgs { if (slashIdx !== -1 && arg.indexOf("/", slashIdx + 1) === -1) { const afterSlash = arg.slice(slashIdx + 1); if (afterSlash && looksLikeIssueShortId(afterSlash)) { + // Use "org/" (trailing slash) to signal OrgAll mode so downstream + // parseOrgProjectArg interprets this as an org, not a project search. return { eventId: "latest", - targetArg: arg.slice(0, slashIdx), + targetArg: `${arg.slice(0, slashIdx)}/`, issueShortId: afterSlash, }; } @@ -511,14 +513,18 @@ async function resolveIssueShortcut( } // Issue short ID auto-redirect: user passed an issue short ID - // (e.g., "BRUNCHIE-APP-29") instead of a hex event ID. Resolve - // the issue and show its latest event. + // (e.g., "BRUNCHIE-APP-29" or "figma/FULLSCREEN-2RN") instead of a hex + // event ID. Resolve the issue and show its latest event. if (issueShortId) { log.warn( `'${issueShortId}' is an issue short ID, not an event ID. Showing the latest event.` ); - const resolved = await resolveOrg({ cwd }); + // Use the explicit org from the parsed target if available (e.g., + // "figma/" → org-all with org "figma"), otherwise fall back to + // auto-detection via DSN/env/config. + const explicitOrg = parsed.type === "org-all" ? parsed.org : undefined; + const resolved = await resolveOrg({ org: explicitOrg, cwd }); if (!resolved) { throw new ContextError( "Organization", diff --git a/test/commands/event/view.test.ts b/test/commands/event/view.test.ts index bff29551..9e6babdb 100644 --- a/test/commands/event/view.test.ts +++ b/test/commands/event/view.test.ts @@ -85,14 +85,15 @@ describe("parsePositionalArgs", () => { test("detects org/ISSUE-SHORT-ID pattern (CLI-9K)", () => { const result = parsePositionalArgs(["figma/FULLSCREEN-2RN"]); expect(result.eventId).toBe("latest"); - expect(result.targetArg).toBe("figma"); + // Trailing slash signals OrgAll mode so downstream resolves org correctly + expect(result.targetArg).toBe("figma/"); expect(result.issueShortId).toBe("FULLSCREEN-2RN"); }); test("detects org/CLI-G pattern", () => { const result = parsePositionalArgs(["sentry/CLI-G"]); expect(result.eventId).toBe("latest"); - expect(result.targetArg).toBe("sentry"); + expect(result.targetArg).toBe("sentry/"); expect(result.issueShortId).toBe("CLI-G"); });