Skip to content

feat: add sentry span list and sentry span view commands#393

Open
betegon wants to merge 9 commits intomainfrom
feat/span-commands
Open

feat: add sentry span list and sentry span view commands#393
betegon wants to merge 9 commits intomainfrom
feat/span-commands

Conversation

@betegon
Copy link
Member

@betegon betegon commented Mar 11, 2026

Summary

Adds sentry span list and sentry span view so AI agents can filter/drill-into individual spans within a trace, instead of dealing with the full nested tree dump from sentry trace view --json.

Workflow this enables:

sentry span list <trace-id> --json -q "op:db duration:>100ms" --sort duration
sentry span view <span-id> --trace <trace-id> --json

Changes

Shared utilities (src/lib/formatters/trace.ts):

  • Extracted computeSpanDurationMs from human.ts into trace.ts for reuse
  • flattenSpanTree — converts nested span tree to flat array with depth/child_count
  • findSpanById — searches tree returning span + ancestor chain
  • parseSpanQuery / applySpanFilter — parse and apply -q "op:db duration:>100ms project:backend" filters
  • writeSpanTable / formatSpanDetails — table and detail formatters

sentry span list (src/commands/span/list.ts):

  • Positional: [<org>/<project>] <trace-id> (same pattern as trace view)
  • --query / -q: filter by op, project, description, duration thresholds
  • --sort: time (default, depth-first order) or duration (slowest first)
  • --limit / -n: cap output (default 25, max 1000)
  • JSON mode includes totalSpans and matchedSpans in envelope

sentry span view (src/commands/span/view.ts):

  • Positional: [<org>/<project>] <span-id> [<span-id>...] (multi-ID like log view)
  • --trace / -t: required trace ID
  • --spans: child tree depth (default 3)
  • Shows metadata KV table, ancestor chain, and child span tree
  • JSON mode includes ancestors and children arrays

Route wiring (src/app.ts):

  • sentry span {list, view} route
  • sentry spans plural alias → span list

Test Plan

  • bun run typecheck passes
  • bun run lint passes (0 errors)
  • bun run test:unit — all 3370 passing tests still pass (43 pre-existing failures unrelated)
  • Manual: sentry span list <trace-id> --json --limit 5
  • Manual: sentry span list <trace-id> -q "op:db" --sort duration
  • Manual: sentry span view <span-id> --trace <trace-id> --json
  • Manual: sentry span view <span-id> <span-id> --trace <trace-id>

Closes #391

betegon and others added 2 commits March 11, 2026 14:38
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>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 11, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Init

  • Add --team flag to relay team selection to project creation by MathurAditya724 in #403
  • Enforce canonical feature display order by betegon in #388
  • Accept multiple delimiter formats for --features flag by betegon in #386
  • Add git safety checks before wizard modifies files by betegon in #379
  • Add experimental warning before wizard runs by betegon in #378
  • Add init command for guided Sentry project setup by betegon in #283

Issue List

  • Auto-compact when table exceeds terminal height by BYK in #395
  • Redesign table to match Sentry web UI by BYK in #372

Other

  • (auth) Allow re-authentication without manual logout by BYK in #417
  • (trial) Auto-prompt for Seer trial + sentry trial list/start commands by BYK in #399
  • Add sentry span list and sentry span view commands by betegon in #393
  • Support SENTRY_HOST as alias for SENTRY_URL by betegon in #409
  • Add --dry-run flag to mutating commands by BYK in #387
  • Return-based output with OutputConfig on buildCommand by BYK in #380
  • Add --fields flag for context-window-friendly JSON output by BYK in #373
  • Magic @ selectors (@latest, @most_frequent) for issue commands by BYK in #371
  • Input hardening against agent hallucinations by BYK in #370
  • Add response caching for read-only API calls by BYK in #330

Bug Fixes 🐛

Dsn

Init

  • Make URLs clickable with OSC 8 terminal hyperlinks by MathurAditya724 in #423
  • Remove implementation detail from help text by betegon in #385
  • Truncate uncommitted file list to first 5 entries by MathurAditya724 in #381

Other

  • (api) Convert --data to query params for GET requests by BYK in #383
  • (docs) Remove double borders and fix column alignment on landing page tables by betegon in #369
  • (trace) Show span IDs in trace view and fix event_id mapping by betegon in #400
  • Show human-friendly names in trial list and surface plan trials by BYK in #412
  • Add trace ID validation to trace view + UUID dash-stripping by BYK in #375

Documentation 📚

  • Update credential storage docs and remove stale config.json references by betegon in #408

Internal Changes 🔧

Init

  • Remove --force flag by betegon in #377
  • Remove dead determine-pm step label by betegon in #374

Tests

  • Consolidate unit tests subsumed by property tests by BYK in #422
  • Remove redundant and low-value tests by BYK in #418

Other

  • (log/list) Convert non-follow paths to return CommandOutput by BYK in #410
  • Convert list command handlers to return data instead of writing stdout by BYK in #404
  • Split api-client.ts into focused domain modules by BYK in #405
  • Migrate non-streaming commands to CommandOutput with markdown rendering by BYK in #398
  • Convert Tier 2-3 commands to return-based output and consola by BYK in #394
  • Convert remaining Tier 1 commands to return-based output by BYK in #382
  • Converge Tier 1 commands to writeOutput helper by BYK in #376

Other

  • Minify JSON on read and pretty-print on write in init local ops by MathurAditya724 in #396

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 11, 2026

Codecov Results 📊

111 passed | Total: 111 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests
Passed Tests
Failed Tests
Skipped Tests

✨ No test changes detected

All tests are passing successfully.

❌ Patch coverage is 58.53%. Project has 1222 uncovered lines.
❌ Project coverage is 94.39%. Comparing base (base) to head (head).

Files with missing lines (3)
File Patch % Lines
trace.ts 55.99% ⚠️ 136 Missing
human.ts 96.37% ⚠️ 44 Missing
traces.ts 73.21% ⚠️ 30 Missing
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

betegon and others added 6 commits March 11, 2026 15:40
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>
@betegon betegon marked this pull request as ready for review March 13, 2026 19:48
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

walk(span, 0);
}
return result;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

depth: number,
ancestors: TraceSpan[]
): FoundSpan | null {
if (span.span_id === spanId) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Comment on lines +250 to +253
trace: {
kind: "parsed",
parse: validateTraceId,
brief: "Trace ID containing the span(s) (required)",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add sentry span command group for AI-agent trace debugging

1 participant