@@ -9,7 +9,8 @@ import type { SentryContext } from "../../context.js";
99import { buildOrgAwareAliases } from "../../lib/alias.js" ;
1010import {
1111 findProjectsBySlug ,
12- listIssues ,
12+ ISSUES_MAX_PER_PAGE ,
13+ listIssuesAllPages ,
1314 listIssuesPaginated ,
1415 listProjects ,
1516} from "../../lib/api-client.js" ;
@@ -26,7 +27,12 @@ import {
2627 setProjectAliases ,
2728} from "../../lib/db/project-aliases.js" ;
2829import { createDsnFingerprint } from "../../lib/dsn/index.js" ;
29- import { ApiError , AuthError , ContextError } from "../../lib/errors.js" ;
30+ import {
31+ ApiError ,
32+ AuthError ,
33+ ContextError ,
34+ ValidationError ,
35+ } from "../../lib/errors.js" ;
3036import {
3137 divider ,
3238 type FormatShortIdOptions ,
@@ -76,6 +82,13 @@ const VALID_SORT_VALUES: SortValue[] = ["date", "new", "freq", "user"];
7682/** Usage hint for ContextError messages */
7783const USAGE_HINT = "sentry issue list <org>/<project>" ;
7884
85+ /**
86+ * Maximum --limit value (auto-pagination ceiling).
87+ * Equals MAX_PAGINATION_PAGES * ISSUES_MAX_PER_PAGE — the theoretical maximum
88+ * the auto-paginator can return. Kept lower for practical CLI response times.
89+ */
90+ const MAX_LIMIT = 1000 ;
91+
7992function parseSort ( value : string ) : SortValue {
8093 if ( ! VALID_SORT_VALUES . includes ( value as SortValue ) ) {
8194 throw new Error (
@@ -378,7 +391,11 @@ async function fetchIssuesForTarget(
378391 options : { query ?: string ; limit : number ; sort : SortValue }
379392) : Promise < FetchResult > {
380393 try {
381- const issues = await listIssues ( target . org , target . project , options ) ;
394+ const issues = await listIssuesAllPages (
395+ target . org ,
396+ target . project ,
397+ options
398+ ) ;
382399 return { success : true , data : { target, issues } } ;
383400 } catch ( error ) {
384401 // Auth errors should propagate - user needs to authenticate
@@ -406,6 +423,67 @@ function nextPageHint(org: string, flags: ListFlags): string {
406423 return parts . length > 0 ? `${ base } ${ parts . join ( " " ) } ` : base ;
407424}
408425
426+ /** Result from fetching org-wide issues (with or without cursor). */
427+ type OrgAllFetchResult = {
428+ issues : SentryIssue [ ] ;
429+ nextCursor ?: string ;
430+ } ;
431+
432+ /**
433+ * Fetch org-wide issues, auto-paginating from the start or resuming from a cursor.
434+ *
435+ * When `cursor` is provided (--cursor resume), fetches a single page to keep the
436+ * cursor chain intact. Otherwise auto-paginates up to the requested limit.
437+ */
438+ async function fetchOrgAllIssues (
439+ org : string ,
440+ flags : Pick < ListFlags , "query" | "limit" | "sort" > ,
441+ cursor : string | undefined
442+ ) : Promise < OrgAllFetchResult > {
443+ const perPage = Math . min ( flags . limit , ISSUES_MAX_PER_PAGE ) ;
444+
445+ // When resuming with --cursor, fetch a single page so the cursor chain stays intact.
446+ if ( cursor ) {
447+ const response = await listIssuesPaginated ( org , "" , {
448+ query : flags . query ,
449+ cursor,
450+ perPage,
451+ sort : flags . sort ,
452+ } ) ;
453+ return { issues : response . data , nextCursor : response . nextCursor } ;
454+ }
455+
456+ // No cursor — auto-paginate from the beginning.
457+ const issues : SentryIssue [ ] = [ ] ;
458+ let pageCursor : string | undefined ;
459+ let lastCursor : string | undefined ;
460+
461+ // Safety-bounded at 50 pages to match the auto-paginator in api-client.ts.
462+ const MAX_PAGES = 50 ;
463+ for ( let page = 0 ; issues . length < flags . limit && page < MAX_PAGES ; page ++ ) {
464+ const response = await listIssuesPaginated ( org , "" , {
465+ query : flags . query ,
466+ cursor : pageCursor ,
467+ perPage,
468+ sort : flags . sort ,
469+ } ) ;
470+
471+ issues . push ( ...response . data ) ;
472+ lastCursor = response . nextCursor ;
473+
474+ if ( ! response . nextCursor ) {
475+ break ;
476+ }
477+ pageCursor = response . nextCursor ;
478+ }
479+
480+ // Trim to exact limit (last page may overshoot)
481+ return {
482+ issues : issues . length > flags . limit ? issues . slice ( 0 , flags . limit ) : issues ,
483+ nextCursor : lastCursor ,
484+ } ;
485+ }
486+
409487/** Options for {@link handleOrgAllIssues}. */
410488type OrgAllIssuesOptions = {
411489 stdout : Writer ;
@@ -431,30 +509,25 @@ async function handleOrgAllIssues(options: OrgAllIssuesOptions): Promise<void> {
431509
432510 setContext ( [ org ] , [ ] ) ;
433511
434- const response = await listIssuesPaginated ( org , "" , {
435- query : flags . query ,
436- cursor,
437- perPage : flags . limit ,
438- sort : flags . sort ,
439- } ) ;
512+ const { issues, nextCursor } = await fetchOrgAllIssues ( org , flags , cursor ) ;
440513
441- if ( response . nextCursor ) {
442- setPaginationCursor ( PAGINATION_KEY , contextKey , response . nextCursor ) ;
514+ if ( nextCursor ) {
515+ setPaginationCursor ( PAGINATION_KEY , contextKey , nextCursor ) ;
443516 } else {
444517 clearPaginationCursor ( PAGINATION_KEY , contextKey ) ;
445518 }
446519
447- const hasMore = ! ! response . nextCursor ;
520+ const hasMore = ! ! nextCursor ;
448521
449522 if ( flags . json ) {
450523 const output = hasMore
451- ? { data : response . data , nextCursor : response . nextCursor , hasMore : true }
452- : { data : response . data , hasMore : false } ;
524+ ? { data : issues , nextCursor, hasMore : true }
525+ : { data : issues , hasMore : false } ;
453526 writeJson ( stdout , output ) ;
454527 return ;
455528 }
456529
457- if ( response . data . length === 0 ) {
530+ if ( issues . length === 0 ) {
458531 if ( hasMore ) {
459532 stdout . write (
460533 `No issues on this page. Try the next page: ${ nextPageHint ( org , flags ) } \n`
@@ -469,7 +542,7 @@ async function handleOrgAllIssues(options: OrgAllIssuesOptions): Promise<void> {
469542 // column is needed to identify which project each issue belongs to.
470543 writeListHeader ( stdout , `Issues in ${ org } ` , true ) ;
471544 const termWidth = process . stdout . columns || 80 ;
472- const issuesWithOpts = response . data . map ( ( issue ) => ( {
545+ const issuesWithOpts = issues . map ( ( issue ) => ( {
473546 issue,
474547 formatOptions : {
475548 projectSlug : issue . project ?. slug ?? "" ,
@@ -479,10 +552,10 @@ async function handleOrgAllIssues(options: OrgAllIssuesOptions): Promise<void> {
479552 writeIssueRows ( stdout , issuesWithOpts , termWidth ) ;
480553
481554 if ( hasMore ) {
482- stdout . write ( `\nShowing ${ response . data . length } issues (more available)\n` ) ;
555+ stdout . write ( `\nShowing ${ issues . length } issues (more available)\n` ) ;
483556 stdout . write ( `Next page: ${ nextPageHint ( org , flags ) } \n` ) ;
484557 } else {
485- stdout . write ( `\nShowing ${ response . data . length } issues\n` ) ;
558+ stdout . write ( `\nShowing ${ issues . length } issues\n` ) ;
486559 }
487560}
488561
@@ -664,7 +737,10 @@ export const listCommand = buildCommand({
664737 " sentry issue list <org>/ # all projects in org (trailing / required)\n" +
665738 " sentry issue list <project> # find project across all orgs\n\n" +
666739 `${ targetPatternExplanation ( ) } \n\n` +
667- "In monorepos with multiple Sentry projects, shows issues from all detected projects." ,
740+ "In monorepos with multiple Sentry projects, shows issues from all detected projects.\n\n" +
741+ "The --limit flag specifies the total number of results to fetch (max 1000). " +
742+ "When the limit exceeds the API page size (100), multiple requests are made " +
743+ "automatically. Use --cursor to paginate through larger result sets." ,
668744 } ,
669745 parameters : {
670746 positional : LIST_TARGET_POSITIONAL ,
@@ -675,7 +751,7 @@ export const listCommand = buildCommand({
675751 brief : "Search query (Sentry search syntax)" ,
676752 optional : true ,
677753 } ,
678- limit : buildListLimitFlag ( "issues" , "10 " ) ,
754+ limit : buildListLimitFlag ( "issues" , "25 " ) ,
679755 sort : {
680756 kind : "parsed" ,
681757 parse : parseSort ,
@@ -703,6 +779,17 @@ export const listCommand = buildCommand({
703779
704780 const parsed = parseOrgProjectArg ( target ) ;
705781
782+ // Validate --limit range. Auto-pagination handles the API's 100-per-page
783+ // cap transparently, but we cap the total at MAX_LIMIT for practical CLI
784+ // response times. Use --cursor for paginating through larger result sets.
785+ if ( flags . limit > MAX_LIMIT ) {
786+ throw new ValidationError (
787+ `--limit cannot exceed ${ MAX_LIMIT } . ` +
788+ "Use --cursor to paginate through larger result sets." ,
789+ "limit"
790+ ) ;
791+ }
792+
706793 // biome-ignore lint/suspicious/noExplicitAny: shared handler accepts any mode variant
707794 const resolveAndHandle : ModeHandler < any > = ( ctx ) =>
708795 handleResolvedTargets ( { ...ctx , flags, stderr, setContext } ) ;
0 commit comments