Hive Hive
Sign in

CLI never refreshes account-type credentials and discards their refresh token, so auth breaks permanently at expiry

GitHub issue · Open

Metadata
Source
tuist/tuist #12221
Updated
Aug 5, 2026
Domains
CLI
Details

Summary

When the CLI’s credentials file holds an account-type token, the accompanying refresh token is discarded at parse time and never used. Once the access token expires the CLI keeps presenting it, every authenticated request starts failing, and the stale credentials are never cleaned up — so the failure is permanent until something external rewrites the file.

For credentials written by the Tuist app (see #12220) the access token is short-lived while the refresh token is valid far longer, so this trips reliably.

Mechanism

Three steps, each individually reasonable, that compose into a dead end.

1. The refresh token is dropped when the credentials are read. ServerAuthenticationController.fetchTokenFromStore(serverURL:) maps any credential whose access token carries type == "account" to .account(accessToken). The AuthenticationToken.account case has no refresh-token associated value, so credentials.refreshToken is discarded even when present on disk.

2. The refresh path is a no-op for that case. executeRefresh(serverURL:forceRefresh:):

case let .account(accessToken):
upToDateToken = token
expiresAt = cacheExpirationDate(for: accessToken)

The stored token is returned verbatim regardless of forceRefresh. For an already-expired token this hands back an expired bearer with an expiresAt in the past.

3. Nothing detects the resulting failure. tokenStatus correctly reports .expired once the access token is within 30s of expiry, but the refresh path above returns the same token anyway. ServerClientAuthenticationMiddleware attaches it, and the server rejects the request. The recovery path — deletingCredentialsOnUnauthorizedError — only fires on RefreshAuthTokenServiceError.unauthorized, which is never raised here because no refresh request is ever made. The stale credentials stay on disk indefinitely.

Impact

Analytics uploads, and any other authenticated CLI operation, stop working once the access token lapses. The failure is quiet: run metadata upload failures are logged at warning level and the command still succeeds, so from the outside runs simply stop appearing in the dashboard. tuist auth whoami is no help either, since it reports “You are not logged in” for account tokens whether they are valid or expired (#12219).

Proposed fix

Carry the refresh token through for account credentials and refresh them like user credentials — the refresh endpoint accepts them, and the credentials file already contains everything needed. Concretely, AuthenticationToken.account needs the refresh token alongside the access token so executeRefresh can act on it.

Failing that, at minimum the expired-and-unrefreshable case should surface as an actionable error and clear the credentials, rather than repeatedly sending a token that cannot work.

Related

  • #12220 — how these credentials end up in the CLI’s store
  • #12219 — whoami masking the state
  • #12218 — attribution loss while the token is still valid
Flights

Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.

New Flights are paused Configure model inference, GitHub, and a sandbox provider to start another Flight. Existing results remain available below.
No Flights yet

Start a Flight and preserve its objective, outcome, and session here.

Comments

No GitHub comments yet.