88import type { SentryContext } from "../../context.js" ;
99import { buildOrgAwareAliases } from "../../lib/alias.js" ;
1010import {
11+ API_MAX_PER_PAGE ,
1112 findProjectsBySlug ,
12- listIssues ,
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,12 @@ 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 (user-facing ceiling for practical CLI response times).
87+ * Auto-pagination can theoretically fetch more, but 1000 keeps responses reasonable.
88+ */
89+ const MAX_LIMIT = 1000 ;
90+
7991function parseSort ( value : string ) : SortValue {
8092 if ( ! VALID_SORT_VALUES . includes ( value as SortValue ) ) {
8193 throw new Error (
@@ -378,7 +390,11 @@ async function fetchIssuesForTarget(
378390 options : { query ?: string ; limit : number ; sort : SortValue }
379391) : Promise < FetchResult > {
380392 try {
381- const issues = await listIssues ( target . org , target . project , options ) ;
393+ const { issues } = await listIssuesAllPages (
394+ target . org ,
395+ target . project ,
396+ options
397+ ) ;
382398 return { success : true , data : { target, issues } } ;
383399 } catch ( error ) {
384400 // Auth errors should propagate - user needs to authenticate
@@ -406,6 +422,44 @@ function nextPageHint(org: string, flags: ListFlags): string {
406422 return parts . length > 0 ? `${ base } ${ parts . join ( " " ) } ` : base ;
407423}
408424
425+ /** Result from fetching org-wide issues (with or without cursor). */
426+ type OrgAllFetchResult = {
427+ issues : SentryIssue [ ] ;
428+ nextCursor ?: string ;
429+ } ;
430+
431+ /**
432+ * Fetch org-wide issues, auto-paginating from the start or resuming from a cursor.
433+ *
434+ * When `cursor` is provided (--cursor resume), fetches a single page to keep the
435+ * cursor chain intact. Otherwise auto-paginates up to the requested limit.
436+ */
437+ async function fetchOrgAllIssues (
438+ org : string ,
439+ flags : Pick < ListFlags , "query" | "limit" | "sort" > ,
440+ cursor : string | undefined
441+ ) : Promise < OrgAllFetchResult > {
442+ // When resuming with --cursor, fetch a single page so the cursor chain stays intact.
443+ if ( cursor ) {
444+ const perPage = Math . min ( flags . limit , API_MAX_PER_PAGE ) ;
445+ const response = await listIssuesPaginated ( org , "" , {
446+ query : flags . query ,
447+ cursor,
448+ perPage,
449+ sort : flags . sort ,
450+ } ) ;
451+ return { issues : response . data , nextCursor : response . nextCursor } ;
452+ }
453+
454+ // No cursor — auto-paginate from the beginning via the shared helper.
455+ const { issues, nextCursor } = await listIssuesAllPages ( org , "" , {
456+ query : flags . query ,
457+ limit : flags . limit ,
458+ sort : flags . sort ,
459+ } ) ;
460+ return { issues, nextCursor } ;
461+ }
462+
409463/** Options for {@link handleOrgAllIssues}. */
410464type OrgAllIssuesOptions = {
411465 stdout : Writer ;
@@ -431,30 +485,25 @@ async function handleOrgAllIssues(options: OrgAllIssuesOptions): Promise<void> {
431485
432486 setContext ( [ org ] , [ ] ) ;
433487
434- const response = await listIssuesPaginated ( org , "" , {
435- query : flags . query ,
436- cursor,
437- perPage : flags . limit ,
438- sort : flags . sort ,
439- } ) ;
488+ const { issues, nextCursor } = await fetchOrgAllIssues ( org , flags , cursor ) ;
440489
441- if ( response . nextCursor ) {
442- setPaginationCursor ( PAGINATION_KEY , contextKey , response . nextCursor ) ;
490+ if ( nextCursor ) {
491+ setPaginationCursor ( PAGINATION_KEY , contextKey , nextCursor ) ;
443492 } else {
444493 clearPaginationCursor ( PAGINATION_KEY , contextKey ) ;
445494 }
446495
447- const hasMore = ! ! response . nextCursor ;
496+ const hasMore = ! ! nextCursor ;
448497
449498 if ( flags . json ) {
450499 const output = hasMore
451- ? { data : response . data , nextCursor : response . nextCursor , hasMore : true }
452- : { data : response . data , hasMore : false } ;
500+ ? { data : issues , nextCursor, hasMore : true }
501+ : { data : issues , hasMore : false } ;
453502 writeJson ( stdout , output ) ;
454503 return ;
455504 }
456505
457- if ( response . data . length === 0 ) {
506+ if ( issues . length === 0 ) {
458507 if ( hasMore ) {
459508 stdout . write (
460509 `No issues on this page. Try the next page: ${ nextPageHint ( org , flags ) } \n`
@@ -469,7 +518,7 @@ async function handleOrgAllIssues(options: OrgAllIssuesOptions): Promise<void> {
469518 // column is needed to identify which project each issue belongs to.
470519 writeListHeader ( stdout , `Issues in ${ org } ` , true ) ;
471520 const termWidth = process . stdout . columns || 80 ;
472- const issuesWithOpts = response . data . map ( ( issue ) => ( {
521+ const issuesWithOpts = issues . map ( ( issue ) => ( {
473522 issue,
474523 formatOptions : {
475524 projectSlug : issue . project ?. slug ?? "" ,
@@ -479,10 +528,10 @@ async function handleOrgAllIssues(options: OrgAllIssuesOptions): Promise<void> {
479528 writeIssueRows ( stdout , issuesWithOpts , termWidth ) ;
480529
481530 if ( hasMore ) {
482- stdout . write ( `\nShowing ${ response . data . length } issues (more available)\n` ) ;
531+ stdout . write ( `\nShowing ${ issues . length } issues (more available)\n` ) ;
483532 stdout . write ( `Next page: ${ nextPageHint ( org , flags ) } \n` ) ;
484533 } else {
485- stdout . write ( `\nShowing ${ response . data . length } issues\n` ) ;
534+ stdout . write ( `\nShowing ${ issues . length } issues\n` ) ;
486535 }
487536}
488537
@@ -664,7 +713,10 @@ export const listCommand = buildCommand({
664713 " sentry issue list <org>/ # all projects in org (trailing / required)\n" +
665714 " sentry issue list <project> # find project across all orgs\n\n" +
666715 `${ targetPatternExplanation ( ) } \n\n` +
667- "In monorepos with multiple Sentry projects, shows issues from all detected projects." ,
716+ "In monorepos with multiple Sentry projects, shows issues from all detected projects.\n\n" +
717+ "The --limit flag specifies the number of results to fetch per project (max 1000). " +
718+ "When the limit exceeds the API page size (100), multiple requests are made " +
719+ "automatically. Use --cursor to paginate through larger result sets." ,
668720 } ,
669721 parameters : {
670722 positional : LIST_TARGET_POSITIONAL ,
@@ -675,7 +727,7 @@ export const listCommand = buildCommand({
675727 brief : "Search query (Sentry search syntax)" ,
676728 optional : true ,
677729 } ,
678- limit : buildListLimitFlag ( "issues" , "10 " ) ,
730+ limit : buildListLimitFlag ( "issues" , "25 " ) ,
679731 sort : {
680732 kind : "parsed" ,
681733 parse : parseSort ,
@@ -703,6 +755,20 @@ export const listCommand = buildCommand({
703755
704756 const parsed = parseOrgProjectArg ( target ) ;
705757
758+ // Validate --limit range. Auto-pagination handles the API's 100-per-page
759+ // cap transparently, but we cap the total at MAX_LIMIT for practical CLI
760+ // response times. Use --cursor for paginating through larger result sets.
761+ if ( flags . limit < 1 ) {
762+ throw new ValidationError ( "--limit must be at least 1." , "limit" ) ;
763+ }
764+ if ( flags . limit > MAX_LIMIT ) {
765+ throw new ValidationError (
766+ `--limit cannot exceed ${ MAX_LIMIT } . ` +
767+ "Use --cursor to paginate through larger result sets." ,
768+ "limit"
769+ ) ;
770+ }
771+
706772 // biome-ignore lint/suspicious/noExplicitAny: shared handler accepts any mode variant
707773 const resolveAndHandle : ModeHandler < any > = ( ctx ) =>
708774 handleResolvedTargets ( { ...ctx , flags, stderr, setContext } ) ;
0 commit comments