Skip to content

feat(cli): Pick the stored account from the target org or server - #1528

Open
sentry-junior[bot] wants to merge 10 commits into
mainfrom
feat/cli-account-routing
Open

sentry-junior[bot] wants to merge 10 commits into
mainfrom
feat/cli-account-routing

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Stacked on #1526. Covers the automatic-selection half of #1396: with several stored accounts, commands use the account that fits what they target, so users don't have to sentry auth switch back and forth.

How the account is chosen (per invocation, most specific first)

  1. Organization. If the target org isn't in the active account's cached org list, but another stored account has used that org, that account is pinned for the rest of the invocation. This hooks into resolveOrgRegion / resolveEffectiveOrg, so it covers the ~55 org-scoped call sites and DSN oNNNN ids.
  2. Server. An explicit SENTRY_HOST/SENTRY_URL, .sentryclirc, or a pasted Sentry URL for another account's server selects the stored account on that server. Previously this was a HostScopeError.
  3. Active account otherwise.

Guardrails

  • The stored active account never changes. The selection lives in memory, keyed by the invocation's env object, so library-mode calls are isolated from each other.
  • One invocation never mixes identities: the first pin wins.
  • An account is only chosen for hosts its token is already trusted for, using the same SaaS-equivalence rule as token-host.ts.
  • If the active account has no cached org list (for example right after auth switch), it is kept. An account that simply hasn't used the org yet can't be overridden by one that has.
  • An explicit server limits candidates to accounts on that server. The persisted defaults.url counts as soft: when the pinned account is elsewhere, it yields to that account's host.
  • SENTRY_FORCE_ENV_TOKEN is never overridden.

Storage
Schema v19 adds org_regions.account_id. Writes record the account whose credentials fetched the row, following in-process refresh-token rotation. The migration backfills existing rows by matching credential identity. The org→account mapping is warm right away for new logins, because login already warms the org cache.

Memoized credential state (token, fingerprint, row, stored-creds flag) is now keyed by selection, and user info reads and writes go to the account in effect. Account management (switch, logout, list) still works on the stored active account.

Not covered

  • An org that no stored account has used yet still goes to the active account. There is no retry-on-403 across accounts.
  • When several non-active accounts know the same org, the most recently used one wins, with no prompt.

via byk.

--

View Junior Session [Sentry]

@vercel

vercel Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Oct 17, 2026 6:20am UTC

Request Review

@sentry-junior
sentry-junior Bot added this pull request to stack #1529 October 16, 2026 20:39
@sentry-junior
sentry-junior Bot force-pushed the feat/cli-account-routing branch from b9d3f2c to a9699d8 Compare October 16, 2026 21:06
Comment thread packages/cli/src/lib/db/accounts.ts Outdated
Comment thread packages/cli/src/lib/db/accounts.ts Outdated
Comment thread packages/cli/src/lib/db/account-scope.ts
Comment thread packages/cli/src/lib/db/auth.ts
Comment thread packages/cli/src/lib/db/auth.ts Outdated
@sentry-junior
sentry-junior Bot force-pushed the feat/cli-account-routing branch from ad24de9 to 2139d1a Compare October 16, 2026 23:21
@sentry-junior
sentry-junior Bot force-pushed the feat/cli-account-routing branch from 0e933d8 to 46c6dc7 Compare October 17, 2026 00:21
@sentry-junior
sentry-junior Bot force-pushed the feat/cli-account-routing branch from 46c6dc7 to c288b7b Compare October 17, 2026 00:30
@sentry-junior
sentry-junior Bot force-pushed the feat/cli-account-routing branch from c288b7b to 66b331e Compare October 17, 2026 00:37
@BYK
BYK marked this pull request as ready for review October 17, 2026 01:09
@github-actions github-actions Bot added risk: medium PR risk score: medium and removed risk: medium PR risk score: medium labels Oct 17, 2026
@sentry-junior
sentry-junior Bot force-pushed the feat/cli-account-routing branch from 66b331e to dc83d07 Compare October 17, 2026 01:11
@github-actions github-actions Bot added the risk: medium PR risk score: medium label Oct 17, 2026

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/cli/src/lib/db/user.ts
Comment thread packages/cli/src/lib/db/auth.ts

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/cli/src/lib/db/accounts.ts
Comment thread packages/cli/src/lib/db/auth.ts
Base automatically changed from feat/cli-multi-account to main October 17, 2026 05:51
With several stored accounts, commands now use the one that fits what
they target instead of always using the active account:

- an explicit server (SENTRY_HOST/SENTRY_URL, .sentryclirc, or a pasted
  Sentry URL) selects the stored account on that server
- an organization missing from the active account's cached org list
  selects the stored account that has used it, for the rest of the
  invocation

Schema v19 records which account each cached org row belongs to and
backfills existing rows. The stored active account never changes, one
invocation never mixes identities, and forced env tokens still win.
Use `last_used_at` (falling back to `updated_at`) when choosing the most
recently used account for an org or server, matching logout promotion.
`hasCachedOrgList` ignored `updated_at`, so an expired org list still let
org-based selection override the active account. Apply the same 7-day
TTL as `getCachedOrganizations`.
…t account

Push recency ordering and the usable-credential filter into the account
queries instead of sorting and filtering rows in TypeScript. The org lookup
is now one query over the `org_regions.account_id` cache that returns
usable owners most recently used first; only same-server matching (origin
normalization, `*.sentry.io` as one server) stays in code.

Rename the per-invocation selection from "pinned" to the invocation's
default account, and document why the scope is keyed by the env object:
library-mode calls can overlap in one process, each under its own env.
…org cache

A login stored while a command had picked another account for its target
org wrote to that account: re-authentication overwrote its credentials and
recording the user could then delete the active account as a duplicate.
Logins now drop the invocation's picked account first, so they replace the
active account (or add a new one) and the rest of the invocation uses the
new credentials.

Invalidating an account's credentials also only cleared org memberships
cached under its current identity. Rows written before a refresh-token
rotation kept the account id, so a later login on that account inherited
stale memberships. Clear rows by account id too, as account deletion does.
Picking the account for a DSN-style `oNNNN` org only matched cached rows
carrying that numeric id, but resolving a single org by slug caches no id
(only org lists do). An account that had used the org by slug was missed
and the active account kept. Also match rows for the slug that any cached
row on the same server maps the id to.
… authenticate

`selectAccountForOrg` kept the account in effect whenever it had no fresh
cached org list. An account whose credentials were invalidated (token
cleared, org cache dropped) always qualified, so a usable stored account
that knows the org was never picked and the command failed auth. Only
keep the account in effect on that ground when it can still authenticate.
@BYK
BYK force-pushed the feat/cli-account-routing branch from c192232 to a592763 Compare October 17, 2026 05:51

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a592763. Configure here.

Comment thread packages/cli/src/lib/db/auth.ts
Comment thread packages/cli/src/lib/sentry-url-parser.ts
…sts unsnapshotted

Replacing credentials in place (`sentry auth login --force`, automatic
re-login) wrote to the account selected for the configured server, so
SENTRY_HOST/SENTRY_URL or `.sentryclirc` naming another stored account's
server overwrote that account instead of the active one. The legacy NULL
host backfill had the same mix-up. Both now use the stored active account.

Pasting a SaaS URL read the active token host after clearing the URL; with
an env token in effect that snapshotted the token's host as SaaS even when
it was scoped to a self-hosted server. Compare the stored account in
effect instead, which never touches the env-token snapshot.
@BYK

BYK commented Oct 17, 2026

Copy link
Copy Markdown
Member

I wonder if we should modify commands like "org list" to list all available organizations under all accounts. Wdyt?

`sentry auth login --force` (and automatic re-login) replaced the stored
active account even when the login targeted another server, so a
self-hosted login could overwrite a SaaS account and vice versa.

Accounts are now always tied to their server: an in-place login replaces
the active account only when it is on the login's server, otherwise the
most recently used account on that server, and adds a new account when
that server has none. Accounts on other servers are never touched. The
`--force --token` path removes only that server's previous login, after
the new token validates.

Co-Authored-By: Burak Yigit Kaya <burak.kaya@sentry.io>

This branch was previously deployed

1 inactive deployment
Preview — 0570f1c7 Deployed Oct 17, 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.

1 participant