Skip to content

Commit b1749f0

Browse files
authored
fix(event): Prevent immediate retry of rate-limited org in event view fallback (#1566)
This PR fixes an issue where the `sentry event view` command could trigger a "Consecutive HTTP" performance issue (CLI-2Y1). **Root Cause:** When `fetchEventWithContext` falls back to `tryEventFallbacks` and `resolveEventInOrg` hits a 429 (Too Many Requests) rate limit, the error was caught as a generic transient failure. This left `sameOrgSearched` as `false`, causing the subsequent `findEventAcrossOrgs` call to immediately retry the exact same rate-limited endpoint for the same organization. This back-to-back identical request against a rate-limited API endpoint caused the performance issue. **Solution:** 1. Introduce an `orgRateLimited` flag in `tryEventFallbacks`. 2. When `resolveEventInOrg` catches an `ApiError` with status 429, set `orgRateLimited = true`. 3. Modify the `excludeOrgs` logic for `findEventAcrossOrgs` to exclude the organization if `sameOrgSearched` is true (meaning a definitive not-found) OR if `orgRateLimited` is true. This prevents futile immediate retries against rate-limited endpoints. 4. Added a `logger.debug` call to the catch block to satisfy the `no-silent-catch` lint rule. **Verification:** Added a unit test to `test/commands/event/view.test.ts` to specifically cover the 429 rate-limit scenario, ensuring the organization is correctly excluded from the cross-org fallback. Fixes [CLI-2Y1](https://sentry.sentry.io/issues/7721257760/?seerDrawer=true) <sub>`@sentry <feedback>`: Autofix iterates on these changes</sub> <sub>`@sentry stop iterating`: Autofix stops iterating on this run</sub> <sub>This PR was automatically generated by Sentry. You can [adjust this setting](https://sentry.sentry.io/settings/projects/cli/seer/) at any time.</sub> Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
1 parent 3c869e3 commit b1749f0

2 files changed

Lines changed: 36 additions & 4 deletions

File tree

‎src/commands/event/view.ts‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,10 @@ async function tryEventFallbacks(
703703
// Track whether the search completed so we can skip the org in cross-org
704704
// only when we got a definitive "not found" (not a transient failure).
705705
let sameOrgSearched = false;
706+
// Track rate-limit separately: a 429 means the org is reachable but throttled.
707+
// Retrying it immediately in the cross-org fallback would just hit the same
708+
// limit again, producing consecutive identical HTTP requests (CLI-2Y1).
709+
let orgRateLimited = false;
706710
try {
707711
const resolved = await resolveEventInOrg(org, eventId);
708712
sameOrgSearched = true;
@@ -717,15 +721,20 @@ async function tryEventFallbacks(
717721
if (sameOrgError instanceof AuthError) {
718722
throw sameOrgError;
719723
}
720-
// Transient failure — don't mark org as searched so cross-org retries it
724+
if (sameOrgError instanceof ApiError && sameOrgError.status === 429) {
725+
// Rate-limited — exclude the org from cross-org search; an immediate
726+
// retry against the same endpoint would hit the same limit.
727+
orgRateLimited = true;
728+
}
729+
logger.debug("Same-org event lookup failed", sameOrgError);
721730
}
722731

723732
// Cross-org fallback: the event may exist in a different organization.
724-
// Only exclude the org if the same-org search completed successfully
725-
// (returned null). If it threw a transient error, let cross-org retry it.
733+
// Exclude the org when the same-org search completed (returned null) OR
734+
// when it was rate-limited — either way, re-querying it immediately is futile.
726735
try {
727736
const crossOrg = await findEventAcrossOrgs(eventId, {
728-
excludeOrgs: sameOrgSearched ? [org] : undefined,
737+
excludeOrgs: sameOrgSearched || orgRateLimited ? [org] : undefined,
729738
});
730739
if (crossOrg) {
731740
// Use project-scoped phrasing when found in same org (different project)

‎test/commands/event/view.test.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,29 @@ describe("fetchEventWithContext", () => {
13701370
});
13711371
});
13721372

1373+
test("cross-org excludes org when same-org search was rate-limited (429)", async () => {
1374+
vi.spyOn(apiClient, "getEvent").mockRejectedValue(
1375+
new ApiError("Not found", 404)
1376+
);
1377+
// Same-org search hit a rate limit — retrying it immediately is futile (CLI-2Y1)
1378+
vi.spyOn(apiClient, "resolveEventInOrg").mockRejectedValue(
1379+
new ApiError("Too Many Requests", 429)
1380+
);
1381+
const findSpy = vi
1382+
.spyOn(apiClient, "findEventAcrossOrgs")
1383+
.mockResolvedValue(null);
1384+
1385+
await expect(
1386+
fetchEventWithContext(null, "my-org", "my-project", "abc123")
1387+
).rejects.toThrow(ResolutionError);
1388+
1389+
// org must be excluded — re-querying a rate-limited endpoint immediately
1390+
// produces consecutive identical HTTP requests (the Consecutive HTTP issue).
1391+
expect(findSpy).toHaveBeenCalledWith("abc123", {
1392+
excludeOrgs: ["my-org"],
1393+
});
1394+
});
1395+
13731396
test("swallows non-auth cross-org errors and throws ResolutionError", async () => {
13741397
vi.spyOn(apiClient, "getEvent").mockRejectedValue(
13751398
new ApiError("Not found", 404)

0 commit comments

Comments
 (0)