From 2bc5c2fc9746e402b1503987acfe5bd0920e6a82 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 6 Apr 2026 13:00:04 +0000 Subject: [PATCH 1/4] fix(span-view): detect bare span ID and show helpful ContextError (Sentry CLI-SC) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a user passes a single 16-character hex span ID to 'sentry span view' without a trace ID, the command would fall through to validateTraceId which throws a confusing 'Invalid trace ID' error. Now detects this case early and throws a ContextError with a clear message explaining that the trace ID is also required, with actionable suggestions. Co-authored-by: Miguel Betegón --- src/commands/span/view.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/commands/span/view.ts b/src/commands/span/view.ts index 6cf5e9664..1a9f86be3 100644 --- a/src/commands/span/view.ts +++ b/src/commands/span/view.ts @@ -130,6 +130,20 @@ export function parsePositionalArgs(args: string[]): { } } + // Single bare arg that looks like a span ID (16-char hex, no slashes): + // the user forgot the trace ID. Give a targeted ContextError instead of the + // confusing "Invalid trace ID" from validateTraceId(). (CLI-SC) + if (args.length === 1 && !first.includes("/")) { + const normalized = first.trim().toLowerCase().replace(/-/g, ""); + if (SPAN_ID_RE.test(normalized)) { + throw new ContextError("Trace ID and span ID", USAGE_HINT, [ + `'${first}' looks like a span ID (16 characters), not a trace ID`, + `Provide the trace ID first: sentry span view ${normalized}`, + `Use 'sentry trace list' to find trace IDs`, + ]); + } + } + // First arg is trace target (possibly with org/project prefix) const traceTarget = parseSlashSeparatedTraceTarget(first, USAGE_HINT); From 4c837ac136e071332deb8faa3ad7c064beed2750 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 6 Apr 2026 13:05:11 +0000 Subject: [PATCH 2/4] fix(routing): add 'events' plural alias for 'event' route (Sentry CLI-QZ) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Users typing 'sentry events list ' or 'sentry events view ' get an unhelpful OutputError because 'events' isn't a registered route. Every other resource noun has a plural alias (issues, projects, teams, etc.) but 'events' was missing. Added 'events' route alias (like 'sourcemaps' → 'sourcemapRoute') and the PLURAL_TO_SINGULAR entry so the plural hint system can suggest the singular form when extra args are provided. Co-authored-by: Miguel Betegón --- src/app.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app.ts b/src/app.ts index a0f621af8..85b93bdf4 100644 --- a/src/app.ts +++ b/src/app.ts @@ -60,6 +60,7 @@ import { isRouteMap, type RouteMap } from "./lib/introspect.js"; */ const PLURAL_TO_SINGULAR: Record = { dashboards: "dashboard", + events: "event", issues: "issue", orgs: "org", projects: "project", @@ -87,6 +88,7 @@ export const routes = buildRouteMap({ team: teamRoute, issue: issueRoute, event: eventRoute, + events: eventRoute, log: logRoute, sourcemap: sourcemapRoute, sourcemaps: sourcemapRoute, @@ -117,6 +119,7 @@ export const routes = buildRouteMap({ "It provides commands for authentication, viewing issues, and making API calls.", hideRoute: { dashboards: true, + events: true, issues: true, orgs: true, projects: true, From 456df27ec10e1f656fa89d640d58c629066984b2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 6 Apr 2026 13:07:38 +0000 Subject: [PATCH 3/4] fix(platforms): match middle components in platform suggestions (Sentry CLI-WD) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a user types an invalid platform like 'javascript-cloudflare', the suggestion system failed to suggest 'node-cloudflare-workers' and 'node-cloudflare-pages' because 'cloudflare' appears as a middle component (between dashes), not as a prefix or suffix. Added middle-component matching in findSwapMatches() so that the suffix after the first dash is also checked as a substring between dashes in valid platform names. Co-authored-by: Miguel Betegón --- src/lib/platforms.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/platforms.ts b/src/lib/platforms.ts index 69e7c1ec8..1cc061fbd 100644 --- a/src/lib/platforms.ts +++ b/src/lib/platforms.ts @@ -208,11 +208,15 @@ function findSwapMatches(invalid: string): string[] { const suffixPrefix = `${suffix}-`; // Try suffix as a component in other platforms (e.g. "*-hono" for suffix "hono") const suffixSuffix = `-${suffix}`; + // Also match suffix as a middle component (e.g. "cloudflare" in "node-cloudflare-workers") + const suffixMiddle = `-${suffix}-`; for (const p of VALID_PLATFORMS) { if (p.startsWith(suffixPrefix) && p !== swapped) { results.push(p); } else if (p.endsWith(suffixSuffix) && p !== invalid) { results.push(p); + } else if (p.includes(suffixMiddle)) { + results.push(p); } } From 5588e2794099be7a1b564eb798decf55a992bb27 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Mon, 6 Apr 2026 17:30:02 +0000 Subject: [PATCH 4/4] fix: add tests for CLI-SC/CLI-WD fixes and update stale comments in app.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 3 tests for bare span ID detection (CLI-SC): ContextError type, error message content, and dash-formatted span ID handling - Add 1 test for middle-component platform matching (CLI-WD): javascript-cloudflare → node-cloudflare-pages/workers - Update two comments in app.ts that referenced 'events' as an unrecognized token — now a registered route after CLI-QZ fix --- src/app.ts | 4 ++-- test/commands/span/view.test.ts | 26 ++++++++++++++++++++++++++ test/lib/platforms.test.ts | 6 ++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/app.ts b/src/app.ts index 85b93bdf4..4443836e5 100644 --- a/src/app.ts +++ b/src/app.ts @@ -247,7 +247,7 @@ const customText: ApplicationText = { return `${text_en.exceptionWhileParsingArguments(exc, ansiColor)}${pluralHint}`; } - // With defaultCommand: "view", unknown tokens like "events" fill the + // With defaultCommand: "view", unknown tokens like "metrics" fill the // positional slot, then extra args (e.g., CLI-AB) trigger this error. // Check if the first non-route token is a known synonym. const synonymHint = getSynonymSuggestionFromArgv(); @@ -308,7 +308,7 @@ const customText: ApplicationText = { throw exc; } - // Case C: With defaultCommand: "view", unknown tokens like "events" are + // Case C: With defaultCommand: "view", unknown tokens like "metrics" are // silently consumed as the positional arg. The view command fails at the // domain level (e.g., ResolutionError). Check argv for a known synonym // and show the suggestion — skip Sentry capture since these are known diff --git a/test/commands/span/view.test.ts b/test/commands/span/view.test.ts index 3e611a221..f147a082c 100644 --- a/test/commands/span/view.test.ts +++ b/test/commands/span/view.test.ts @@ -222,6 +222,32 @@ describe("parsePositionalArgs", () => { ValidationError ); }); + + test("throws ContextError for bare span ID without trace ID (CLI-SC)", () => { + expect(() => parsePositionalArgs(["a1b2c3d4e5f67890"])).toThrow( + ContextError + ); + }); + + test("bare span ID error identifies the input and suggests correct usage", () => { + try { + parsePositionalArgs(["A1B2C3D4E5F67890"]); + expect.unreachable("Should have thrown"); + } catch (error) { + expect(error).toBeInstanceOf(ContextError); + const msg = (error as ContextError).message; + expect(msg).toContain("looks like a span ID"); + expect(msg).toContain("sentry span view a1b2c3d4e5f67890"); + expect(msg).toContain("sentry trace list"); + } + }); + + test("bare span ID with dashes is still detected (CLI-SC)", () => { + // Some tools format span IDs with dashes + expect(() => parsePositionalArgs(["a1b2-c3d4-e5f6-7890"])).toThrow( + ContextError + ); + }); }); }); diff --git a/test/lib/platforms.test.ts b/test/lib/platforms.test.ts index 5d4e37ab0..7dd46ea78 100644 --- a/test/lib/platforms.test.ts +++ b/test/lib/platforms.test.ts @@ -68,6 +68,12 @@ describe("suggestPlatform", () => { expect(results).toContain("javascript-react"); }); + test("suggests middle-component match: javascript-cloudflare → node-cloudflare-* (CLI-WD)", () => { + const results = suggestPlatform("javascript-cloudflare"); + expect(results).toContain("node-cloudflare-pages"); + expect(results).toContain("node-cloudflare-workers"); + }); + test("returns empty array for garbage input", () => { expect(suggestPlatform("xyzgarbage")).toEqual([]); });