From 90f2f6e3d9bfd8c60479c4c8746a8d9251ea6959 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 29 Jun 2026 12:16:09 +0000 Subject: [PATCH 1/3] fix(log): cap per_page at API_MAX_PER_PAGE and fix hasMore logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The listLogs() and listTraceLogs() API functions sent the raw --limit value (up to 1000) as per_page to the Sentry API, which silently caps at 100. This caused: 1. Requesting --limit 200 would only return 100 items 2. hasMore was computed as logs.length >= flags.limit (100 >= 200 = false), hiding the fact that more data exists from the user Fix: Cap per_page at Math.min(limit, API_MAX_PER_PAGE) in both API functions, and compute hasMore against the effective per-page value so users see the 'more available' hint correctly. Co-authored-by: Miguel Betegón --- src/commands/log/list.ts | 7 +++++-- src/lib/api/logs.ts | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/commands/log/list.ts b/src/commands/log/list.ts index b9b3eb8c8..7c8e30c69 100644 --- a/src/commands/log/list.ts +++ b/src/commands/log/list.ts @@ -10,6 +10,7 @@ import * as Sentry from "@sentry/node-core/light"; import type { SentryContext } from "../../context.js"; import { + API_MAX_PER_PAGE, type LogSortDirection, listLogs, listTraceLogs, @@ -196,7 +197,8 @@ async function executeSingleFetch( }; } - const hasMore = logs.length >= flags.limit; + const effectivePerPage = Math.min(flags.limit, API_MAX_PER_PAGE); + const hasMore = logs.length >= effectivePerPage; const countText = `Showing ${logs.length} log${logs.length === 1 ? "" : "s"}.`; const tip = hasMore ? " Use --limit to show more, or -f to follow." : ""; @@ -501,7 +503,8 @@ async function executeTraceSingleFetch( }; } - const hasMore = logs.length >= flags.limit; + const effectivePerPage = Math.min(flags.limit, API_MAX_PER_PAGE); + const hasMore = logs.length >= effectivePerPage; const countText = `Showing ${logs.length} log${logs.length === 1 ? "" : "s"} for trace ${traceId}.`; const tip = hasMore ? " Use --limit to show more." : ""; diff --git a/src/lib/api/logs.ts b/src/lib/api/logs.ts index d128091f2..f01c8ecd8 100644 --- a/src/lib/api/logs.ts +++ b/src/lib/api/logs.ts @@ -161,7 +161,7 @@ export async function listLogs( field: fields, project: isNumericProject ? [Number(projectSlug)] : undefined, query: fullQuery || undefined, - per_page: options.limit || API_MAX_PER_PAGE, + per_page: Math.min(options.limit || API_MAX_PER_PAGE, API_MAX_PER_PAGE), statsPeriod: options.start || options.end ? undefined @@ -343,7 +343,7 @@ export async function listTraceLogs( : (options.statsPeriod ?? "14d"), start: options.start, end: options.end, - per_page: options.limit ?? API_MAX_PER_PAGE, + per_page: Math.min(options.limit ?? API_MAX_PER_PAGE, API_MAX_PER_PAGE), query: options.query, sort: toApiSort(options.sort), }, From 8452543d0dec8971a26b23490b5adbc519a34ccb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 29 Jun 2026 12:16:19 +0000 Subject: [PATCH 2/3] fix(alert): guard against null/undefined rule names in list and resolve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The alert list commands and rule-resolve helpers accessed rule.name without null guards. When the Sentry API returns alert rules with null or undefined names (e.g. draft rules, data migration artifacts), this crashes with TypeError on .toLowerCase(). Same class of bug as dashboard #1097 which was fixed with title ?? '(untitled)'. Fix: Add ?? '' guards on .toLowerCase() calls in --query filtering, ?? '(untitled)' guards in table rendering, and truthiness checks in name resolution and fuzzy matching. Co-authored-by: Miguel Betegón --- src/commands/alert/issues/list.ts | 6 ++++-- src/commands/alert/issues/rule-resolve.ts | 6 ++++-- src/commands/alert/metrics/list.ts | 6 ++++-- src/commands/alert/metrics/rule-resolve.ts | 6 ++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/commands/alert/issues/list.ts b/src/commands/alert/issues/list.ts index d1ff2685d..1bdef7c2e 100644 --- a/src/commands/alert/issues/list.ts +++ b/src/commands/alert/issues/list.ts @@ -349,7 +349,9 @@ async function handleResolvedTargets( ); const filteredRows = flags.query ? allRows.filter((row) => - row.rule.name.toLowerCase().includes(flags.query?.toLowerCase() ?? "") + (row.rule.name ?? "") + .toLowerCase() + .includes(flags.query?.toLowerCase() ?? "") ) : allRows; @@ -471,7 +473,7 @@ function formatIssueAlertListHuman(result: IssueAlertListResult): string { const tableRows: Row[] = rows.map(({ rule: r, target }) => ({ id: r.id, - name: escapeMarkdownCell(r.name), + name: escapeMarkdownCell(r.name ?? "(untitled)"), ...(isMultiProject && { project: `${target.org}/${target.project}`, }), diff --git a/src/commands/alert/issues/rule-resolve.ts b/src/commands/alert/issues/rule-resolve.ts index 00b2a8a19..a9e38529c 100644 --- a/src/commands/alert/issues/rule-resolve.ts +++ b/src/commands/alert/issues/rule-resolve.ts @@ -133,10 +133,12 @@ export async function resolveIssueAlertRule( for (const target of targets) { const rules = await listAllIssueRulesForTarget(target); for (const r of rules) { - allRuleNames.push(r.name); + if (r.name) { + allRuleNames.push(r.name); + } } const exact = rules.find( - (rule) => rule.name.toLowerCase() === ref.toLowerCase() + (rule) => rule.name && rule.name.toLowerCase() === ref.toLowerCase() ); if (exact) { hits.push({ target, rule: exact }); diff --git a/src/commands/alert/metrics/list.ts b/src/commands/alert/metrics/list.ts index 185a250fb..f70c42d89 100644 --- a/src/commands/alert/metrics/list.ts +++ b/src/commands/alert/metrics/list.ts @@ -347,7 +347,9 @@ async function handleResolvedOrgs( ); const filteredRows = flags.query ? allRows.filter((row) => - row.rule.name.toLowerCase().includes(flags.query?.toLowerCase() ?? "") + (row.rule.name ?? "") + .toLowerCase() + .includes(flags.query?.toLowerCase() ?? "") ) : allRows; @@ -470,7 +472,7 @@ function formatMetricAlertListHuman(result: MetricAlertListResult): string { const tableRows: Row[] = rows.map(({ rule: r, orgSlug }) => ({ id: r.id, - name: escapeMarkdownCell(r.name), + name: escapeMarkdownCell(r.name ?? "(untitled)"), ...(isMultiOrg && { org: orgSlug }), aggregate: r.aggregate, dataset: r.dataset, diff --git a/src/commands/alert/metrics/rule-resolve.ts b/src/commands/alert/metrics/rule-resolve.ts index a7f243e60..671a31dad 100644 --- a/src/commands/alert/metrics/rule-resolve.ts +++ b/src/commands/alert/metrics/rule-resolve.ts @@ -129,10 +129,12 @@ export async function resolveMetricAlertRule( for (const orgSlug of orgSlugs) { const rules = await listAllMetricRulesForOrg(orgSlug); for (const r of rules) { - allNames.push(r.name); + if (r.name) { + allNames.push(r.name); + } } const exact = rules.find( - (rule) => rule.name.toLowerCase() === ref.toLowerCase() + (rule) => rule.name && rule.name.toLowerCase() === ref.toLowerCase() ); if (exact) { hits.push({ orgSlug, rule: exact }); From e6b2670b251542045c952008faeca07933889c34 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 29 Jun 2026 12:16:30 +0000 Subject: [PATCH 3/3] fix(pagination): cap perPage at API_MAX_PER_PAGE in org-all handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit handleOrgAll() in org-list.ts and the project list org-all handler passed flags.limit (up to 1000) directly as perPage to the API. The Sentry API silently caps per_page at 100, causing: 1. --limit 200 returns at most 100 items per page 2. Users think they've seen everything when more data exists This affected all commands using buildOrgListCommand (team list, repo list, release list) and the project list org-all mode. Fix: Cap perPage at Math.min(flags.limit, API_MAX_PER_PAGE). Co-authored-by: Miguel Betegón --- src/commands/project/list.ts | 3 ++- src/lib/org-list.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/commands/project/list.ts b/src/commands/project/list.ts index 8c0907f1e..c52cbe49f 100644 --- a/src/commands/project/list.ts +++ b/src/commands/project/list.ts @@ -12,6 +12,7 @@ import type { SentryContext } from "../../context.js"; import { + API_MAX_PER_PAGE, findProjectsBySlug, getProject, listOrganizations, @@ -451,7 +452,7 @@ export async function handleOrgAll( () => listProjectsPaginated(org, { cursor, - perPage: flags.limit, + perPage: Math.min(flags.limit, API_MAX_PER_PAGE), }) ); diff --git a/src/lib/org-list.ts b/src/lib/org-list.ts index 74eab7d6e..f17cd8239 100644 --- a/src/lib/org-list.ts +++ b/src/lib/org-list.ts @@ -31,6 +31,7 @@ */ import { + API_MAX_PER_PAGE, findProjectsBySlug, listOrganizations, type PaginatedResponse, @@ -465,7 +466,7 @@ export async function handleOrgAll( () => config.listPaginated(org, { cursor, - perPage: flags.limit, + perPage: Math.min(flags.limit, API_MAX_PER_PAGE), }) );