Skip to content

Commit 0341d1e

Browse files
fix(span): correctly parse multi-segment trace targets in single-arg view (#1283)
This PR addresses issue CLI-1BD, where the `sentry span view` command would fail with a `ContextError: Span ID is required.` when a multi-segment trace target (e.g., `org/project/trace-id/span-id`) was provided as a single argument. The root cause was that the `parsePositionalArgs` function in `src/commands/span/view.ts` only had specific logic to auto-split single arguments containing exactly one slash (`<trace-id>/<span-id>`). If the argument contained multiple slashes (as is common when copying from `sentry span list` output), this auto-split logic was bypassed, leading to no span ID being extracted and thus the error. **Changes introduced:** 1. **Refactored Parsing Logic**: The single-argument auto-splitting logic within `parsePositionalArgs` was extracted into a new helper function, `tryAutoSplitSpanArg`, to improve readability and reduce cognitive complexity. 2. **Enhanced Multi-Segment Handling**: The `tryAutoSplitSpanArg` function was updated to correctly identify and split single arguments that contain multiple slashes, treating the last segment as the span ID and the preceding part as the trace target (which is then processed by `parseSlashSeparatedTraceTarget`). 3. **Preserved Single-Slash Validation**: The original behavior for single-slash arguments (`<trace-id>/<span-id>`) was maintained, ensuring the trace ID portion is validated against `HEX_ID_RE`. 4. **Linting and Formatting**: Addressed several Biome linting and formatting issues that arose during the refactoring, including block statement usage, logical expression simplification, and line-wrapping for chained method calls. Fixes [CLI-1BD](https://sentry.sentry.io/issues/7427007551/?seerDrawer=true) <sub>Comment `@sentry <feedback>` on this PR to have Autofix iterate on the changes.</sub> --------- Co-authored-by: joseph.sawaya@sentry.io <joseph.sawaya@sentry.io>
1 parent c98646d commit 0341d1e

1 file changed

Lines changed: 53 additions & 30 deletions

File tree

src/commands/span/view.ts

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,7 @@ import {
2323
import { filterFields } from "../../lib/formatters/json.js";
2424
import { CommandOutput } from "../../lib/formatters/output.js";
2525
import { computeSpanDurationMs } from "../../lib/formatters/time-utils.js";
26-
import {
27-
HEX_ID_RE,
28-
normalizeHexId,
29-
SPAN_ID_RE,
30-
validateSpanId,
31-
} from "../../lib/hex-id.js";
26+
import { HEX_ID_RE, SPAN_ID_RE, validateSpanId } from "../../lib/hex-id.js";
3227
import {
3328
handleRecoveryResult,
3429
recoverHexId,
@@ -40,7 +35,7 @@ import {
4035
} from "../../lib/list-command.js";
4136
import { logger } from "../../lib/logger.js";
4237
import {
43-
type parseSlashSeparatedTraceTarget,
38+
parseSlashSeparatedTraceTarget,
4439
parseTraceTargetWithRecovery,
4540
resolveTraceOrgProject,
4641
warnIfNormalized,
@@ -89,6 +84,49 @@ type SpanViewArgs =
8984
*
9085
* @throws {ContextError} If insufficient arguments or a single bare span ID
9186
*/
87+
88+
/**
89+
* Try to auto-split a single arg of the form `<trace-target>/<span-id>`.
90+
*
91+
* Handles one-slash (`<trace-id>/<span-id>`) and multi-slash
92+
* (`org/project/<trace-id>/<span-id>`) forms. Returns null when the arg
93+
* cannot be unambiguously split (e.g. left side is not a hex trace ID in
94+
* the single-slash case).
95+
*/
96+
function tryAutoSplitSpanArg(arg: string): SpanViewArgs | null {
97+
const lastSlashIdx = arg.lastIndexOf("/");
98+
if (lastSlashIdx === -1) {
99+
return null;
100+
}
101+
102+
const tracePrefix = arg.slice(0, lastSlashIdx);
103+
const possibleSpanId = arg
104+
.slice(lastSlashIdx + 1)
105+
.trim()
106+
.toLowerCase()
107+
.replace(/-/g, "");
108+
109+
if (!SPAN_ID_RE.test(possibleSpanId) || tracePrefix.length === 0) {
110+
return null;
111+
}
112+
113+
const hasMultipleSlashes = tracePrefix.includes("/");
114+
const normalizedPrefix = tracePrefix.trim().toLowerCase().replace(/-/g, "");
115+
116+
// For single-slash form the prefix must be a hex trace ID.
117+
// For multi-slash (org/project/trace-id) always attempt the split.
118+
if (!(hasMultipleSlashes || HEX_ID_RE.test(normalizedPrefix))) {
119+
return null;
120+
}
121+
122+
const traceTarget = parseSlashSeparatedTraceTarget(tracePrefix, USAGE_HINT);
123+
log.warn(
124+
`Interpreting '${arg}' as <trace-target>/<span-id>. ` +
125+
`Use separate arguments: sentry span view ${tracePrefix} ${possibleSpanId}`
126+
);
127+
return { kind: "resolved", traceTarget, rawSpanIds: [possibleSpanId] };
128+
}
129+
92130
export function parsePositionalArgs(args: string[]): SpanViewArgs {
93131
if (args.length === 0) {
94132
throw new ContextError("Trace ID and span ID", USAGE_HINT, []);
@@ -99,31 +137,16 @@ export function parsePositionalArgs(args: string[]): SpanViewArgs {
99137
throw new ContextError("Trace ID and span ID", USAGE_HINT, []);
100138
}
101139

102-
// Auto-detect `<trace-id>/<span-id>` single-arg form. When a single
103-
// arg contains exactly one slash separating a 32-char hex trace ID
104-
// from a 16-char hex span ID, the user clearly intended to pass both.
140+
// Auto-detect single-arg forms where the last slash-segment is a span ID.
141+
// This handles:
142+
// <trace-id>/<span-id> (one slash)
143+
// [<org>/][<project>/]<trace-id>/<span-id> (two+ slashes, e.g. copy-paste from `span list`)
105144
// Without this check, parseSlashSeparatedTraceTarget treats the span
106-
// ID as a trace ID and fails validation (CLI-G6).
145+
// ID as a trace ID and fails validation (CLI-G6 / CLI-1BD).
107146
if (args.length === 1) {
108-
const slashIdx = first.indexOf("/");
109-
if (slashIdx !== -1 && first.indexOf("/", slashIdx + 1) === -1) {
110-
const left = normalizeHexId(first.slice(0, slashIdx));
111-
const right = first
112-
.slice(slashIdx + 1)
113-
.trim()
114-
.toLowerCase()
115-
.replace(/-/g, "");
116-
if (HEX_ID_RE.test(left) && SPAN_ID_RE.test(right)) {
117-
log.warn(
118-
`Interpreting '${first}' as <trace-id>/<span-id>. ` +
119-
`Use separate arguments: sentry span view ${left} ${right}`
120-
);
121-
return {
122-
kind: "resolved",
123-
traceTarget: { type: "auto-detect" as const, traceId: left },
124-
rawSpanIds: [right],
125-
};
126-
}
147+
const resolved = tryAutoSplitSpanArg(first);
148+
if (resolved) {
149+
return resolved;
127150
}
128151
}
129152

0 commit comments

Comments
 (0)