feat: add sentry span list and sentry span view commands#393
feat: add sentry span list and sentry span view commands#393
sentry span list and sentry span view commands#393Conversation
Move computeSpanDurationMs from human.ts to trace.ts and add shared utilities for the upcoming span commands: flattenSpanTree, findSpanById, parseSpanQuery, applySpanFilter, writeSpanTable, and formatSpanDetails. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add span as a first-class command group for AI-agent trace debugging. - span list: flatten and filter spans in a trace with -q "op:db duration:>100ms", --sort time|duration, --limit - span view: drill into specific spans by ID with --trace, shows metadata, ancestor chain, and child tree - spans: plural alias routes to span list Closes #391 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨Init
Issue List
Other
Bug Fixes 🐛Dsn
Init
Other
Documentation 📚
Internal Changes 🔧Init
Tests
Other
Other
🤖 This preview updates automatically when you update the PR. |
Codecov Results 📊✅ 111 passed | Total: 111 | Pass Rate: 100% | Execution Time: 0ms 📊 Comparison with Base Branch
✨ No test changes detected All tests are passing successfully. ❌ Patch coverage is 58.53%. Project has 1222 uncovered lines. Files with missing lines (3)
Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
- Coverage 95.03% 94.39% -0.64%
==========================================
Files 159 162 +3
Lines 21441 21785 +344
Branches 0 0 —
==========================================
+ Hits 20376 20563 +187
- Misses 1065 1222 +157
- Partials 0 0 —Generated by Codecov Action |
The UUID dash-stripping is already handled silently by validateHexId. Remove the validateAndWarn wrappers, mergeWarnings helper, and all related warning assertions from tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Duration filter `>` vs `>=` was wrong — `duration:>100ms` included 100ms instead of excluding it. Added exclusive/inclusive tracking to SpanFilter and extracted duration comparison helpers. Also made `span view --json` always return an array for consistent output shape. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ce view Use the server-side spans search endpoint (dataset=spans) for `span list` instead of fetching the full trace tree and filtering client-side. Add `translateSpanQuery` to rewrite CLI shorthand keys (op→span.op, duration→span.duration) for the API. Also fix trace view showing `undefined` for span IDs — the trace detail API returns `event_id` instead of `span_id`, so normalize in `getDetailedTrace`. Append span IDs (dimmed) to each tree line. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move listSpans from monolithic api-client.ts into api/traces.ts module to align with main's refactored re-export structure. Keep span_id display without null coalescing (TraceSpan.span_id is required). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| walk(span, 0); | ||
| } | ||
| return result; | ||
| } |
There was a problem hiding this comment.
Exported flattenSpanTree function is never used
Low Severity
flattenSpanTree is exported from trace.ts (and re-exported via the barrel index.ts) but is never imported or called anywhere in the codebase. The span list command fetches spans from the EAP API and converts them via spanListItemToFlatSpan instead, while span view uses findSpanById. This function appears to be dead code left over from an earlier design that converted trace tree data client-side rather than querying the spans endpoint directly.
| depth: number, | ||
| ancestors: TraceSpan[] | ||
| ): FoundSpan | null { | ||
| if (span.span_id === spanId) { |
There was a problem hiding this comment.
Case-sensitive span ID matching may miss results
Medium Severity
validateSpanId lowercases user input via .toLowerCase(), but findSpanById uses strict equality (span.span_id === spanId) against API-returned span IDs. If the trace API ever returns span IDs with uppercase hex characters (e.g., from event_id in normalizeTraceSpan), the comparison silently fails and the command reports "span not found" even though the span exists in the trace tree.
Additional Locations (1)
| trace: { | ||
| kind: "parsed", | ||
| parse: validateTraceId, | ||
| brief: "Trace ID containing the span(s) (required)", |
There was a problem hiding this comment.
Bug: The trace flag in span view is not explicitly marked as required in a way the framework might enforce, potentially causing a crash if the flag is omitted.
Severity: HIGH
Suggested Fix
To make the requirement explicit and prevent runtime errors, either add a default value that forces a parse error (e.g., default: undefined as any as string) or ensure the framework has a mechanism like optional: false to enforce the flag's presence.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/commands/span/view.ts#L250-L253
Potential issue: The `trace` flag for the `span view` command is defined with `kind:
"parsed"` but lacks a `default` value or an `optional` property. While documentation
marks it as required, if the Stricli framework does not enforce this at parse time,
`flags.trace` will be `undefined`. This `undefined` value is then passed to
`validateTraceId`, which will attempt to call `.trim()` on it, leading to a runtime
crash. The code does not defensively check for `undefined` before using the flag's
value.
Did we get this right? 👍 / 👎 to inform future reviews.
| project: isNumericProject ? projectSlug : undefined, | ||
| query: fullQuery || undefined, | ||
| per_page: options.limit || 10, | ||
| statsPeriod: options.statsPeriod ?? "7d", |
There was a problem hiding this comment.
Bug: The span list command hardcodes a 7-day search window (statsPeriod), causing it to silently fail for traces older than 7 days without offering a way to override it.
Severity: HIGH
Suggested Fix
Add a --period flag to the span list command, similar to the one in the trace logs command. This flag should pass its value to the listSpans API call, allowing users to specify the search window and correctly query older traces.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/lib/api/traces.ts#L209
Potential issue: The new `span list` command calls the `listSpans` API function, which
has a hardcoded default `statsPeriod` of "7d". This means the command will silently fail
to find spans for any trace older than 7 days, returning "No spans matched" without
explanation. Unlike the similar `trace logs` command, `span list` does not expose a
`--period` flag to allow users to override this default. This leads to inconsistent
behavior and incorrect results for users analyzing older traces.
Did we get this right? 👍 / 👎 to inform future reviews.


Summary
Adds
sentry span listandsentry span viewso AI agents can filter/drill-into individual spans within a trace, instead of dealing with the full nested tree dump fromsentry trace view --json.Workflow this enables:
Changes
Shared utilities (
src/lib/formatters/trace.ts):computeSpanDurationMsfromhuman.tsintotrace.tsfor reuseflattenSpanTree— converts nested span tree to flat array with depth/child_countfindSpanById— searches tree returning span + ancestor chainparseSpanQuery/applySpanFilter— parse and apply-q "op:db duration:>100ms project:backend"filterswriteSpanTable/formatSpanDetails— table and detail formatterssentry span list(src/commands/span/list.ts):[<org>/<project>] <trace-id>(same pattern astrace view)--query/-q: filter by op, project, description, duration thresholds--sort:time(default, depth-first order) orduration(slowest first)--limit/-n: cap output (default 25, max 1000)totalSpansandmatchedSpansin envelopesentry span view(src/commands/span/view.ts):[<org>/<project>] <span-id> [<span-id>...](multi-ID likelog view)--trace/-t: required trace ID--spans: child tree depth (default 3)Route wiring (
src/app.ts):sentry span {list, view}routesentry spansplural alias →span listTest Plan
bun run typecheckpassesbun run lintpasses (0 errors)bun run test:unit— all 3370 passing tests still pass (43 pre-existing failures unrelated)sentry span list <trace-id> --json --limit 5sentry span list <trace-id> -q "op:db" --sort durationsentry span view <span-id> --trace <trace-id> --jsonsentry span view <span-id> <span-id> --trace <trace-id>Closes #391