Repository navigation
Conversation
Refresh an expired or near-expiry refreshable token while reporting status, unless --no-refresh is given, and show whether the token is short-lived, when it expires, and whether it was just refreshed. Expose the refreshable and expiry fields through the JSON output. Co-authored-by: Copilot <[email protected]> Copilot-Session: 2dc3f23c-61a6-43a7-85cd-90aa872a7fc6
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Rejected refresh tokens produce misleading status output, and refreshable credentials can be incorrectly labeled short-lived.
Review effort: Balanced
Findings: 1
What changed in this PR
Updates gh auth status to refresh and report refreshable credentials.
Changes:
- Adds automatic refresh with
--no-refreshopt-out. - Exposes token expiry metadata in human and JSON output.
- Adds refresh and output tests.
| File | Description |
|---|---|
pkg/cmd/auth/status/status.go |
Resolves refreshable credentials and displays expiry details. |
pkg/cmd/auth/status/status_test.go |
Tests refresh behavior, flag parsing, and JSON output. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| sb.WriteString(fmt.Sprintf(" - Token: %s\n", cs.Bold(e.Token))) | ||
|
|
||
| if e.Refreshable { | ||
| sb.WriteString(" - Short-lived token that gh refreshes automatically\n") |
There was a problem hiding this comment.
That doesn't seem right. If something does allow these tokens to be non-expiring that should probably be cleaned up elsewhere.
| Refreshable: opts.credential.IsRefreshable(), | ||
| TokenExpiresAt: opts.credential.ExpiresAt, | ||
| RefreshTokenExpiresAt: opts.credential.RefreshTokenExpiresAt, | ||
| justRefreshed: opts.refreshStatus == gh.RefreshStatusDone, |
There was a problem hiding this comment.
If a user's refresh token has been rejected (expired, already used, revoked) and they run gh auth status to see what's wrong, the refresh path from PR 2 deletes that account's stored credential before anything is printed. resolveCredential then throws away both the error and the RefreshStatusExpired result, and the only thing that reaches here is whether the token was refreshed. So buildEntry carries on with an empty credential and sends the scope check with Authorization: token (nothing after it).
gh auth token and the git credential helper in this stack both handle RefreshStatusExpired explicitly:
cli/pkg/cmd/auth/token/token.go
Lines 129 to 138 in f0e23cf
This should probably do the same: carry the refresh status into the entry, skip the scope request, and print a clear reason. Something like:
// buildEntry
if opts.refreshStatus == gh.RefreshStatusExpired {
entry.State = authEntryStateError
entry.Error = "refresh token was rejected; the stored credential was removed"
return entry // don't send an empty-token request
}The text output would need a matching line for this case, and the token source shouldn't be left blank.

Part of #14449. Based on #14453 (auth token).
Description
Teaches
gh auth statusto show short-lived token details and to refresh the token as part of reporting status, so what it displays reflects a currently valid credential rather than a stale or expired one.How did you test this change?
This feature is verified end to end as a whole rather than per PR. End-to-end tests should pass.
Key points
gh auth token, the git credential helper). That means a short-lived token whose access token has expired is refreshed and reported as valid, rather than being surfaced as broken. Status reflects the credential gh would actually use, not a stale snapshot.gh auth statusconcurrently with another gh process cannot double-spend the refresh token.Notes for reviewers
Focus on the status output formatting for the refreshable case and the point where resolving status triggers a refresh. Confirm the non-refreshable path is unchanged.
Commit:
fix(auth status): show and refresh short-lived token detailsAuthorship and follow-up
Who wrote this:
Who answers review comments: