Skip to content

Commit 0108c1e

Browse files
cursor[bot]cursoragentbetegonBYK
authored
fix: three bug fixes from Sentry telemetry (CLI-SC, CLI-QZ, CLI-WD) (#664)
## Summary Three independent bug fixes discovered from Sentry telemetry data, each in a separate commit. --- ### 1. fix(span-view): detect bare span ID and show helpful ContextError (CLI-SC) **Sentry Issue:** [CLI-SC](https://sentry.sentry.io/issues/CLI-SC) — 7 users, 12 events **Root cause:** When a user runs `sentry span view <span-id>` with only a 16-char span ID, the code falls through to `validateTraceId()` which throws a confusing "Invalid trace ID" error. **Reproduction:** `sentry span view c38bb3754a4ac6d8` **Fix:** Added early detection in `parsePositionalArgs()` for a single bare arg matching span ID format (16-char hex). Throws a targeted `ContextError` with actionable suggestions instead of the misleading `ValidationError`. --- ### 2. fix(routing): add 'events' plural alias for 'event' route (CLI-QZ) **Sentry Issue:** [CLI-QZ](https://sentry.sentry.io/issues/CLI-QZ) — 47 users, 113 events **Root cause:** Users typing `sentry events list <issue-id>` 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. **Reproduction:** `sentry events list FULLPHYSIO-WEB-3C` **Fix:** Added `events` as a route alias for `eventRoute` (like `sourcemaps` → `sourcemapRoute`) and added `events: "event"` to `PLURAL_TO_SINGULAR` so the plural hint system suggests the correct singular form. --- ### 3. fix(platforms): match middle components in platform suggestions (CLI-WD) **Sentry Issue:** [CLI-WD](https://sentry.sentry.io/issues/CLI-WD) — 2 users, 2 events **Root cause:** When a user types `javascript-cloudflare`, the suggestion system fails to suggest `node-cloudflare-workers` and `node-cloudflare-pages` because `cloudflare` appears as a middle component between dashes, not as a prefix or suffix. **Reproduction:** `sentry project create <name> javascript-cloudflare` **Fix:** Added middle-component matching in `findSwapMatches()` so that components appearing between dashes in valid platform names are also found. <div><a href="https://cursor.com/agents/bc-03af229b-5278-4c3c-acd4-8be47d1b4288"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/automations/95624600-485e-4461-829e-6b76f086c473"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/view-automation-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/view-automation-light.png"><img alt="View Automation" width="141" height="28" src="https://cursor.com/assets/images/view-automation-dark.png"></picture></a>&nbsp;</div> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Miguel Betegón <miguelbetegongarcia@gmail.com> Co-authored-by: Burak Yigit Kaya <byk@sentry.io>
1 parent bda701b commit 0108c1e

File tree

5 files changed

+55
-2
lines changed

5 files changed

+55
-2
lines changed

src/app.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import { isRouteMap, type RouteMap } from "./lib/introspect.js";
6060
*/
6161
const PLURAL_TO_SINGULAR: Record<string, string> = {
6262
dashboards: "dashboard",
63+
events: "event",
6364
issues: "issue",
6465
orgs: "org",
6566
projects: "project",
@@ -87,6 +88,7 @@ export const routes = buildRouteMap({
8788
team: teamRoute,
8889
issue: issueRoute,
8990
event: eventRoute,
91+
events: eventRoute,
9092
log: logRoute,
9193
sourcemap: sourcemapRoute,
9294
sourcemaps: sourcemapRoute,
@@ -117,6 +119,7 @@ export const routes = buildRouteMap({
117119
"It provides commands for authentication, viewing issues, and making API calls.",
118120
hideRoute: {
119121
dashboards: true,
122+
events: true,
120123
issues: true,
121124
orgs: true,
122125
projects: true,
@@ -244,7 +247,7 @@ const customText: ApplicationText = {
244247
return `${text_en.exceptionWhileParsingArguments(exc, ansiColor)}${pluralHint}`;
245248
}
246249

247-
// With defaultCommand: "view", unknown tokens like "events" fill the
250+
// With defaultCommand: "view", unknown tokens like "metrics" fill the
248251
// positional slot, then extra args (e.g., CLI-AB) trigger this error.
249252
// Check if the first non-route token is a known synonym.
250253
const synonymHint = getSynonymSuggestionFromArgv();
@@ -305,7 +308,7 @@ const customText: ApplicationText = {
305308
throw exc;
306309
}
307310

308-
// Case C: With defaultCommand: "view", unknown tokens like "events" are
311+
// Case C: With defaultCommand: "view", unknown tokens like "metrics" are
309312
// silently consumed as the positional arg. The view command fails at the
310313
// domain level (e.g., ResolutionError). Check argv for a known synonym
311314
// and show the suggestion — skip Sentry capture since these are known

src/commands/span/view.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,20 @@ export function parsePositionalArgs(args: string[]): {
130130
}
131131
}
132132

133+
// Single bare arg that looks like a span ID (16-char hex, no slashes):
134+
// the user forgot the trace ID. Give a targeted ContextError instead of the
135+
// confusing "Invalid trace ID" from validateTraceId(). (CLI-SC)
136+
if (args.length === 1 && !first.includes("/")) {
137+
const normalized = first.trim().toLowerCase().replace(/-/g, "");
138+
if (SPAN_ID_RE.test(normalized)) {
139+
throw new ContextError("Trace ID and span ID", USAGE_HINT, [
140+
`'${first}' looks like a span ID (16 characters), not a trace ID`,
141+
`Provide the trace ID first: sentry span view <trace-id> ${normalized}`,
142+
`Use 'sentry trace list' to find trace IDs`,
143+
]);
144+
}
145+
}
146+
133147
// First arg is trace target (possibly with org/project prefix)
134148
const traceTarget = parseSlashSeparatedTraceTarget(first, USAGE_HINT);
135149

src/lib/platforms.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,15 @@ function findSwapMatches(invalid: string): string[] {
208208
const suffixPrefix = `${suffix}-`;
209209
// Try suffix as a component in other platforms (e.g. "*-hono" for suffix "hono")
210210
const suffixSuffix = `-${suffix}`;
211+
// Also match suffix as a middle component (e.g. "cloudflare" in "node-cloudflare-workers")
212+
const suffixMiddle = `-${suffix}-`;
211213
for (const p of VALID_PLATFORMS) {
212214
if (p.startsWith(suffixPrefix) && p !== swapped) {
213215
results.push(p);
214216
} else if (p.endsWith(suffixSuffix) && p !== invalid) {
215217
results.push(p);
218+
} else if (p.includes(suffixMiddle)) {
219+
results.push(p);
216220
}
217221
}
218222

test/commands/span/view.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,32 @@ describe("parsePositionalArgs", () => {
222222
ValidationError
223223
);
224224
});
225+
226+
test("throws ContextError for bare span ID without trace ID (CLI-SC)", () => {
227+
expect(() => parsePositionalArgs(["a1b2c3d4e5f67890"])).toThrow(
228+
ContextError
229+
);
230+
});
231+
232+
test("bare span ID error identifies the input and suggests correct usage", () => {
233+
try {
234+
parsePositionalArgs(["A1B2C3D4E5F67890"]);
235+
expect.unreachable("Should have thrown");
236+
} catch (error) {
237+
expect(error).toBeInstanceOf(ContextError);
238+
const msg = (error as ContextError).message;
239+
expect(msg).toContain("looks like a span ID");
240+
expect(msg).toContain("sentry span view <trace-id> a1b2c3d4e5f67890");
241+
expect(msg).toContain("sentry trace list");
242+
}
243+
});
244+
245+
test("bare span ID with dashes is still detected (CLI-SC)", () => {
246+
// Some tools format span IDs with dashes
247+
expect(() => parsePositionalArgs(["a1b2-c3d4-e5f6-7890"])).toThrow(
248+
ContextError
249+
);
250+
});
225251
});
226252
});
227253

test/lib/platforms.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ describe("suggestPlatform", () => {
6868
expect(results).toContain("javascript-react");
6969
});
7070

71+
test("suggests middle-component match: javascript-cloudflare → node-cloudflare-* (CLI-WD)", () => {
72+
const results = suggestPlatform("javascript-cloudflare");
73+
expect(results).toContain("node-cloudflare-pages");
74+
expect(results).toContain("node-cloudflare-workers");
75+
});
76+
7177
test("returns empty array for garbage input", () => {
7278
expect(suggestPlatform("xyzgarbage")).toEqual([]);
7379
});

0 commit comments

Comments
 (0)