feat: add sentry span list and sentry span view commands#393
Conversation
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 92.63%. Project has 1101 uncovered lines. Files with missing lines (4)
Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
- Coverage 95.10% 95.02% -0.08%
==========================================
Files 159 163 +4
Lines 21516 22103 +587
Branches 0 0 —
==========================================
+ Hits 20462 21002 +540
- Misses 1054 1101 +47
- 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>
…ring
Migrate span list/view from imperative output to auto-rendered OutputConfig
pattern, and fix plain-mode ANSI leaks in span tree/ancestor formatting.
**Output system migration (span/list.ts, span/view.ts):**
- Replace `output: "json"` shorthand with `output: { json: true, human: fn, jsonTransform: fn }`
- Extract human formatters (formatSpanListHuman, formatSpanViewHuman) that
return strings instead of writing to stdout directly
- Extract JSON transforms (jsonTransformSpanList, jsonTransformSpanView) for
the { data: [...], hasMore } envelope and --fields filtering
- Return `{ data, hint }` from func() so the wrapper handles rendering
- Remove manual `if (flags.json)` branching and direct stdout writes
**Markdown rendering fixes (trace.ts, human.ts):**
- formatAncestorChain: replace raw `muted()` (chalk ANSI) with
`colorTag("muted", ...)` + `renderMarkdown()` so output respects
NO_COLOR/isPlainOutput/non-TTY
- formatSimpleSpanTree/formatSpanSimple: replace `muted()` with
`plainSafeMuted()` that checks `isPlainOutput()` before applying ANSI
- Span list header now renders via `renderMarkdown()` for proper styling
**Formatter exports (trace.ts):**
- Export SPAN_TABLE_COLUMNS for use by span/list formatter
- Add formatSpanTable() return-based wrapper around formatTable()
After merging main, command.ts uses async generators + CommandOutput class:
- Remove 'json: true' from OutputConfig (no longer needed on main)
- Change async func → async *func (generator)
- Replace return { data, hint } → yield new CommandOutput(data) + return { hint }
- Import CommandOutput from formatters/output.js
- Remove dead code: flattenSpanTree (unused, superseded by EAP API) - Remove dead code: writeSpanTable (superseded by formatSpanTable) - Fix case-insensitive span ID matching in findSpanById — lowercase the API-returned span_id before comparing with user-lowercased input - Fix JSON duration consistency: use computeSpanDurationMs() in buildJsonResults instead of raw r.span.duration, matching the human output path's timestamp-arithmetic fallback
|
Addressed all Cursor BugBot feedback in f7198cc:
|
The orgSlugArb and projectSlugArb generators could produce strings starting with 'xn--' (punycode-encoded IDN labels). When used as a subdomain (e.g. 'xn--0a.sentry.io'), the URL constructor silently decodes the punycode, collapsing the hostname to 'sentry.io' and dropping the org. This caused flaky failures in the buildTraceUrl → parseSentryUrl round-trip test. Fix: filter out xn-- prefixed slugs since real Sentry org/project slugs are never punycode-encoded.
- Restore null fallback for span_id in formatSpanSimple: span.span_id ?? '' (defensive against runtime undefined from normalizeTraceSpan edge cases) - Unexport SPAN_TABLE_COLUMNS — only used internally by formatSpanTable, no external consumers
Use optional chaining (span.span_id?.toLowerCase()) to avoid crash when a span from the API has neither span_id nor event_id.
UX improvement: Replace --trace flag with positional argument to match the standardized [<org>/<project>/]<id> pattern used by span list, trace view, and event view. Before: sentry span view <span-id> --trace <trace-id> After: sentry span view [<org>/<project>/]<trace-id> <span-id> This is consistent with span list which already uses: sentry span list [<org>/<project>/]<trace-id> SKILL.md: Update placeholder text from generic '<args...>' to descriptive '<org/project/trace-id...>' and '<trace-id/span-id...>'. Tests: Add comprehensive tests for both span commands: - span/list: parsePositionalArgs, parseSort, and func body tests (API calls, query translation, org/project resolution) → 86.71% coverage - span/view: validateSpanId, parsePositionalArgs, and func body tests (span lookup, JSON output, multi-span, child tree) → 86.89% coverage - formatters/trace: findSpanById (case-insensitive, undefined span_id), computeSpanDurationMs, spanListItemToFlatSpan
span list used 'time' while trace list used 'date' — both mapped to the same -timestamp API sort. Standardize on 'date'/'duration' to match trace list and issue list conventions. Also removes 'time' from the listSpans API type since it was redundant with 'date'. Refs: PR #393 review feedback
- Use SortValue type alias in ListFlags instead of inline literal union - Fix MAX_LIMIT JSDoc: removed incorrect claim about internal pagination (listSpans passes per_page directly, no client-side pagination) - Add default params to validateLimit(value, min=1, max=1000) - Change limit brief to (<=) format
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Extract formatRelativeTime and computeSpanDurationMs into a new time-utils.ts module. Both are pure utility functions that were causing a circular dependency: trace.ts → human.ts (formatRelativeTime) human.ts → trace.ts (computeSpanDurationMs) Now both import from time-utils.ts which only depends on types/ and markdown.ts — no cycle. Update all direct importers (span/view.ts, test files) to use the new module path.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Add --cursor/-c flag for page-through pagination, matching the pattern used by trace list, issue list, and log list. - Wire up shared pagination infrastructure: buildPaginationContextKey, resolveOrgCursor, setPaginationCursor, clearPaginationCursor - Context key scoped to org/project/traceId + sort/query to prevent cursor collisions across different queries - Include nextCursor in JSON output envelope - Show '-c last' hint in footer when more pages available - Support 'sentry span list <trace-id> -c last' to resume The listSpans API already supported cursor — this just wires it up in the command layer.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Consolidate two separate imports from time-utils.js into a single import statement at the top of the file. Removes the mid-file import that was left from the circular import refactor.
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.
- translateSpanQuery: strip leading '!' before alias lookup, re-add after resolution. '!op:db' now correctly translates to '!span.op:db'. - FlatSpan: remove unused depth/child_count fields (remnants of removed flattenSpanTree function).
Major updates based on lessons learned during PR #393: 1. CLI Commands section: Replace deprecated stdout.write() pattern with the current async generator + CommandOutput + OutputConfig pattern. Add explicit rules about buildCommand import source. 2. New sections: Positional Arguments (parseSlashSeparatedArg pattern), Markdown Rendering (renderMarkdown/colorTag rules, plainSafeMuted), List Command Pagination (cursor infrastructure), ID Validation (hex-id.ts validators), Sort Convention ('date' not 'time'), SKILL.md (generate:skill, descriptive placeholders). 3. Architecture tree: Add missing directories (span/, trace/, log/, trial/, cli/, api/), files (command.ts, hex-id.ts, trace-id.ts, pagination.ts, time-utils.ts, markdown.ts, trace.ts, table.ts), and fix stale descriptions (api-client.ts is barrel, not ky-based). 4. List Command Infrastructure: Add standalone list command pattern (span list, trace list) as third tier alongside buildOrgListCommand and dispatchOrgScopedList. 5. Imports: Fix stale @stricli/core import in example.
Major updates based on lessons learned during PR #393: 1. CLI Commands section: Replace deprecated stdout.write() pattern with the current async generator + CommandOutput + OutputConfig pattern. Add explicit rules about buildCommand import source. 2. New sections: Positional Arguments (parseSlashSeparatedArg pattern), Markdown Rendering (renderMarkdown/colorTag rules, plainSafeMuted), List Command Pagination (cursor infrastructure), ID Validation (hex-id.ts validators), Sort Convention ('date' not 'time'), SKILL.md (generate:skill, descriptive placeholders). 3. Architecture tree: Add missing directories (span/, trace/, log/, trial/, cli/, api/), files (command.ts, hex-id.ts, trace-id.ts, pagination.ts, time-utils.ts, markdown.ts, trace.ts, table.ts), and fix stale descriptions (api-client.ts is barrel, not ky-based). 4. List Command Infrastructure: Add standalone list command pattern (span list, trace list) as third tier alongside buildOrgListCommand and dispatchOrgScopedList. 5. Imports: Fix stale @stricli/core import in example.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Update AGENTS.md to document patterns established during #393 (span commands). These were discovered through multiple review cycles and should prevent future contributors from repeating the same mistakes. ## Changes **CLI Commands section** — Replace deprecated `stdout.write()` example with the current `async *func` + `CommandOutput` + `OutputConfig` pattern. Add explicit warning against importing `buildCommand` from `@stricli/core`. **New subsections** under Key Patterns: - **Positional Arguments** — `parseSlashSeparatedArg` for `[<org>/<project>/]<id>` - **Markdown Rendering** — `renderMarkdown()`/`colorTag()` rules; `plainSafeMuted` for tree output - **List Command Pagination** — Full cursor infrastructure pattern with code example - **ID Validation** — `validateHexId`, `validateSpanId`, `validateTraceId` from `hex-id.ts` - **Sort Convention** — Use `"date"` not `"time"` for timestamp sorts - **SKILL.md** — `generate:skill`, descriptive placeholders **Architecture tree** — Add 15+ missing directories and files (`span/`, `trace/`, `log/`, `api/`, `command.ts`, `hex-id.ts`, `pagination.ts`, `time-utils.ts`, `markdown.ts`, etc.) **List Command Infrastructure** — Add standalone list command pattern as third tier. Refs #393
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.

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