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 ,
16+ MAX_PAGINATION_PAGES ,
1517} from "../../lib/api-client.js" ;
1618import { parseOrgProjectArg } from "../../lib/arg-parsing.js" ;
1719import { buildCommand } from "../../lib/command.js" ;
@@ -26,7 +28,12 @@ import {
2628 setProjectAliases ,
2729} from "../../lib/db/project-aliases.js" ;
2830import { createDsnFingerprint } from "../../lib/dsn/index.js" ;
29- import { ApiError , AuthError , ContextError } from "../../lib/errors.js" ;
31+ import {
32+ ApiError ,
33+ AuthError ,
34+ ContextError ,
35+ ValidationError ,
36+ } from "../../lib/errors.js" ;
3037import {
3138 divider ,
3239 type FormatShortIdOptions ,
@@ -76,6 +83,12 @@ const VALID_SORT_VALUES: SortValue[] = ["date", "new", "freq", "user"];
7683/** Usage hint for ContextError messages */
7784const USAGE_HINT = "sentry issue list <org>/<project>" ;
7885
86+ /**
87+ * Maximum --limit value (auto-pagination ceiling).
88+ * Derived from the page safety bound × the API's per-page cap.
89+ */
90+ const MAX_LIMIT = MAX_PAGINATION_PAGES * API_MAX_PER_PAGE ;
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,44 @@ 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+ // When resuming with --cursor, fetch a single page so the cursor chain stays intact.
444+ if ( cursor ) {
445+ const perPage = Math . min ( flags . limit , API_MAX_PER_PAGE ) ;
446+ const response = await listIssuesPaginated ( org , "" , {
447+ query : flags . query ,
448+ cursor,
449+ perPage,
450+ sort : flags . sort ,
451+ } ) ;
452+ return { issues : response . data , nextCursor : response . nextCursor } ;
453+ }
454+
455+ // No cursor — auto-paginate from the beginning via the shared helper.
456+ const { issues, nextCursor } = await listIssuesAllPages ( org , "" , {
457+ query : flags . query ,
458+ limit : flags . limit ,
459+ sort : flags . sort ,
460+ } ) ;
461+ return { issues, nextCursor } ;
462+ }
463+
409464/** Options for {@link handleOrgAllIssues}. */
410465type OrgAllIssuesOptions = {
411466 stdout : Writer ;
@@ -431,30 +486,25 @@ async function handleOrgAllIssues(options: OrgAllIssuesOptions): Promise<void> {
431486
432487 setContext ( [ org ] , [ ] ) ;
433488
434- const response = await listIssuesPaginated ( org , "" , {
435- query : flags . query ,
436- cursor,
437- perPage : flags . limit ,
438- sort : flags . sort ,
439- } ) ;
489+ const { issues, nextCursor } = await fetchOrgAllIssues ( org , flags , cursor ) ;
440490
441- if ( response . nextCursor ) {
442- setPaginationCursor ( PAGINATION_KEY , contextKey , response . nextCursor ) ;
491+ if ( nextCursor ) {
492+ setPaginationCursor ( PAGINATION_KEY , contextKey , nextCursor ) ;
443493 } else {
444494 clearPaginationCursor ( PAGINATION_KEY , contextKey ) ;
445495 }
446496
447- const hasMore = ! ! response . nextCursor ;
497+ const hasMore = ! ! nextCursor ;
448498
449499 if ( flags . json ) {
450500 const output = hasMore
451- ? { data : response . data , nextCursor : response . nextCursor , hasMore : true }
452- : { data : response . data , hasMore : false } ;
501+ ? { data : issues , nextCursor, hasMore : true }
502+ : { data : issues , hasMore : false } ;
453503 writeJson ( stdout , output ) ;
454504 return ;
455505 }
456506
457- if ( response . data . length === 0 ) {
507+ if ( issues . length === 0 ) {
458508 if ( hasMore ) {
459509 stdout . write (
460510 `No issues on this page. Try the next page: ${ nextPageHint ( org , flags ) } \n`
@@ -469,7 +519,7 @@ async function handleOrgAllIssues(options: OrgAllIssuesOptions): Promise<void> {
469519 // column is needed to identify which project each issue belongs to.
470520 writeListHeader ( stdout , `Issues in ${ org } ` , true ) ;
471521 const termWidth = process . stdout . columns || 80 ;
472- const issuesWithOpts = response . data . map ( ( issue ) => ( {
522+ const issuesWithOpts = issues . map ( ( issue ) => ( {
473523 issue,
474524 formatOptions : {
475525 projectSlug : issue . project ?. slug ?? "" ,
@@ -479,10 +529,10 @@ async function handleOrgAllIssues(options: OrgAllIssuesOptions): Promise<void> {
479529 writeIssueRows ( stdout , issuesWithOpts , termWidth ) ;
480530
481531 if ( hasMore ) {
482- stdout . write ( `\nShowing ${ response . data . length } issues (more available)\n` ) ;
532+ stdout . write ( `\nShowing ${ issues . length } issues (more available)\n` ) ;
483533 stdout . write ( `Next page: ${ nextPageHint ( org , flags ) } \n` ) ;
484534 } else {
485- stdout . write ( `\nShowing ${ response . data . length } issues\n` ) ;
535+ stdout . write ( `\nShowing ${ issues . length } issues\n` ) ;
486536 }
487537}
488538
@@ -664,7 +714,10 @@ export const listCommand = buildCommand({
664714 " sentry issue list <org>/ # all projects in org (trailing / required)\n" +
665715 " sentry issue list <project> # find project across all orgs\n\n" +
666716 `${ targetPatternExplanation ( ) } \n\n` +
667- "In monorepos with multiple Sentry projects, shows issues from all detected projects." ,
717+ "In monorepos with multiple Sentry projects, shows issues from all detected projects.\n\n" +
718+ "The --limit flag specifies the total number of results to fetch (max 1000). " +
719+ "When the limit exceeds the API page size (100), multiple requests are made " +
720+ "automatically. Use --cursor to paginate through larger result sets." ,
668721 } ,
669722 parameters : {
670723 positional : LIST_TARGET_POSITIONAL ,
@@ -675,7 +728,7 @@ export const listCommand = buildCommand({
675728 brief : "Search query (Sentry search syntax)" ,
676729 optional : true ,
677730 } ,
678- limit : buildListLimitFlag ( "issues" , "10 " ) ,
731+ limit : buildListLimitFlag ( "issues" , "25 " ) ,
679732 sort : {
680733 kind : "parsed" ,
681734 parse : parseSort ,
@@ -703,6 +756,20 @@ export const listCommand = buildCommand({
703756
704757 const parsed = parseOrgProjectArg ( target ) ;
705758
759+ // Validate --limit range. Auto-pagination handles the API's 100-per-page
760+ // cap transparently, but we cap the total at MAX_LIMIT for practical CLI
761+ // response times. Use --cursor for paginating through larger result sets.
762+ if ( flags . limit < 1 ) {
763+ throw new ValidationError ( "--limit must be at least 1." , "limit" ) ;
764+ }
765+ if ( flags . limit > MAX_LIMIT ) {
766+ throw new ValidationError (
767+ `--limit cannot exceed ${ MAX_LIMIT } . ` +
768+ "Use --cursor to paginate through larger result sets." ,
769+ "limit"
770+ ) ;
771+ }
772+
706773 // biome-ignore lint/suspicious/noExplicitAny: shared handler accepts any mode variant
707774 const resolveAndHandle : ModeHandler < any > = ( ctx ) =>
708775 handleResolvedTargets ( { ...ctx , flags, stderr, setContext } ) ;
0 commit comments