Skip to content

feat(api): add per-request retry and cache controls - #1649

Open
betegon wants to merge 2 commits into
mainfrom
bt/api-request-options
Open

betegon wants to merge 2 commits into
mainfrom
bt/api-request-options

Conversation

@betegon

@betegon betegon commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds optional per-request controls to getSdkConfig(regionUrl, options):

  • retry: false sends the request exactly once: no retry on 408/429/5xx, network errors or timeouts, and no 401 refresh-and-replay.
  • cache: "no-store" skips both the lookup and the write in the local response cache.

Callers that pass no options keep the shared memoized fetch. Both options exist for sentry issue link / unlink (#1559).

Why

retry: false. Linking through a Sentry App (Linear, for example) goes through POST /sentry-app-installations/{uuid}/external-issue-actions/. Sentry calls the App's webhook synchronously and only then stores the association, so the external tracker may already have acted when the request fails with a 5xx, a timeout or a dropped connection. The shared fetch retries every method on those failures, up to twice, so one issue link could run the App's callback up to three times. The backend guard from getsentry/sentry#124069 turns a retry after a stored link into a no-op, but it cannot help when the callback ran and the write did not, and it explicitly does not promise exactly-once callbacks. For this request the CLI should report the failure and leave re-running to the user. Dropping the 401 replay there is harmless: a 401 is rejected before the endpoint runs, so the user just sees the auth error.

cache: "no-store". issue link / unlink read the issue's current associations to decide what to send: unlink maps the URL to the stored association ID, and link uses the stored canonical URL as its expectedExternalIssueUrl guard. By default those GETs fall into the response cache's 60-second issue tier (5 minutes for Sentry App installations, components and choice lookups). If someone links the issue in the web UI after the CLI last read the list, a cached read makes unlink report "already unlinked" and leave the association in place, and --dry-run shows the wrong state. Skipping the write too keeps a pre-mutation snapshot out of the cache for later commands.

--fresh (disableResponseCache()) doesn't fit: it is process-wide and never reset, so in SDK/library mode one sdk.issue.link() call would turn off cache reads for the rest of the host process.

The 401 fix. While wiring retry: false I found that a 401 on the last attempt still refreshed the token and asked for another attempt, so the loop threw Exhausted all retry attempts instead of returning the 401 and its auth guidance. The last attempt now returns the 401. That is also what lets retry: false be a plain single-attempt loop, without a separate code path.

Validation

  • The final-attempt 401 test fails on main with Exhausted all retry attempts.
  • Both retry: false tests fail if the option is ignored; the 401 one uses a refreshable OAuth session, so it checks that no refresh happens.
  • tsc --noEmit and lint pass. Unit suite: 473 files, 10,096 passed / 17 skipped (TZ=UTC).

Split out of #1559.

betegon and others added 2 commits September 29, 2026 08:37
A 401 on the last retry still refreshed the token and asked for another attempt, so the loop ended with "Exhausted all retry attempts" instead of the 401 and its auth guidance.

Co-authored-by: Cursor <cursoragent@cursor.com>
Callers can send a request exactly once (retry: false) for mutations whose side effects happen before a failure can be reported, and bypass the local response cache (cache: "no-store") for preflight reads that authorize a mutation. The default shared fetch is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
cli Ready Ready Preview Sep 29, 2026 6:43am UTC
1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Sep 29, 2026 6:43am UTC

Request Review

betegon added a commit that referenced this pull request Sep 29, 2026
Keep sentry-client.ts identical to #1649 so merging it leaves no diff here. A final-attempt 401 now returns the response, which makes retry: false a plain single-attempt loop without its own code path.

Co-authored-by: Cursor <cursoragent@cursor.com>
@BYK
BYK marked this pull request as ready for review September 29, 2026 07:11
@BYK

BYK commented Sep 29, 2026

Copy link
Copy Markdown
Member

Looks fine but we need the "why" of this change in the PR description. Why do we need this exactly?

@github-actions github-actions Bot added the risk: medium PR risk score: medium label Sep 29, 2026
@cursor

cursor Bot commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Creating a rollout plan

Mention @change-monitor in a comment to update the plan.

@betegon

betegon commented Sep 29, 2026

Copy link
Copy Markdown
Member Author

Oops, I was splitting up the link/unlink external issues PR (#1559) and it got a bit out of hand; this could have stayed in that PR. We need it there for two things: the Sentry App link callback runs before Sentry stores the association, so our default retries could fire it up to three times, and the link/unlink preflight reads can't come from the response cache, or unlink misses a link created in the UI within the last minute. Updated the description with the details.

This branch was successfully deployed

1 active and 1 inactive deployments
Preview – cli — 77e4bda9 Deployed Sep 29, 2026 by vercel[bot]
Preview – sentry-local — 77e4bda9 Deployed Sep 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants