Skip to content

feat(integrations): Add a provider access token RPC for cells - #128490

Merged
vaind merged 4 commits into
masterfrom
ivan/integration-access-token-rpc
Oct 22, 2026
Merged

vaind merged 4 commits into
masterfrom
ivan/integration-access-token-rpc

Conversation

@vaind

@vaind vaind commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Sentry's upcoming git endpoint (CW-2222) runs in a cell and forwards git requests to the provider with an installation's access token. A cell can't mint or refresh a token, because the app keys and refresh tokens live in control. Git also can't recover from a 401. So the cell asks control for a token that's still valid.

This adds integration_service.get_access_token(integration_id, organization_id) -> RpcAccessToken | None for GitHub, GitHub Enterprise and Cursor Origin. Control uses each provider's client to return the stored token if it has at least five minutes left, and otherwise mints a new one. It sends back only the token and when it expires. The token is kept out of repr, and the expiry lets a caller hold on to the token until shortly before then.

Like refresh_github_access_token, it's scoped to an organization: it returns None unless that organization has an active install of an active integration with that id. It also returns None when the provider refuses or fails, and logs why without including the provider's response. Unlike refresh_github_access_token, which returns the whole integration with its metadata (for GitHub Enterprise that includes the App's private key), only the access token leaves control. GitHub's client now also returns expires_at from get_access_token, as Cursor Origin's already does.

GitLab joins in a follow-up once #128442 lands. Today GitLab's client hands out its stored token without checking whether it has expired, so this RPC couldn't promise a usable one. #128443 fixes how Cursor Origin stores a newly minted token, and lands independently.

Code outside control that hands a provider token on, such as the git endpoint, can't mint or refresh one, and can't retry after a 401. get_access_token mints or refreshes the token in control and returns only it and its expiry, for GitHub, GitHub Enterprise, GitLab and Cursor Origin. Refresh tokens and app keys stay in control, unlike with refresh_github_access_token, which returns the whole integration with its metadata.

GitHub's client now also returns the expiry from get_access_token, as Cursor Origin's already does.

Refs CW-2222
Comment thread src/sentry/integrations/services/integration/impl.py
vaind added 2 commits October 22, 2026 09:25
… refreshes early

On master GitLab's client returns its stored token without checking its expiry, so the RPC couldn't promise a valid GitLab token; GitLab joins once its client refreshes ahead of expiry. With the remaining providers all taking a minimum validity, pass the same five minutes to each instead of relying on their defaults.

Also log when a client returns no token, let an unparseable expiry raise instead of reading as never expiring, catch an install removed during the call, and test GitHub Enterprise minting.

Refs CW-2222
@vaind
vaind added this pull request to stack #128492 October 22, 2026 07:39
@vaind
vaind marked this pull request as ready for review October 22, 2026 07:47
@vaind
vaind requested review from a team as code owners October 22, 2026 07:47
Comment thread src/sentry/integrations/services/integration/service.py Outdated
Comment thread src/sentry/integrations/services/integration/impl.py Outdated
@vaind
vaind merged commit 1ac69d4 into master Oct 22, 2026
89 checks passed
@vaind
vaind deleted the ivan/integration-access-token-rpc branch October 22, 2026 12:45
vaind added a commit that referenced this pull request Oct 22, 2026
…a token (#128443)

Minting a Cursor Origin token saved the whole integration row as it was
read before the request to Origin. A rename or an uninstall that landed
during the request was therefore undone, and a row deleted meanwhile
came back. The provider access token RPC in
#128490 mints on demand for git
requests, which makes that overlap likely.

The row is now re-read after the request, and only `metadata` and
`date_updated` are written, with a single `UPDATE` like
`update_integration` uses. Nothing is stored if the row is gone. There's
no row lock, since a `select_for_update` on a control table has stalled
the control database before. The only change that can still be lost is a
metadata write that lands between the re-read and the update, a window
of a few milliseconds rather than the whole request.

A mint response that isn't an object, or lacks a token or an expiry, now
raises `ApiError` instead of `KeyError` or `AttributeError`, so callers
handle it like any other failed mint.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants