Print gh auth refresh for 401 returns

`gh auth refresh` exists to make it simpler for users to refresh their tokens on expiration/scope mismatch, but help messages only suggest using it in limited scenarios, and not in a common case of a token expiring and the user receiving a 401 error. Now, the auth flow will detect this case, and for refreshable tokens (namely, tokens created by logging in with `gh auth login` in the first place), it will suggest using `gh auth refresh` for these cases.
This commit is contained in:
Fredric Silberberg 2026-03-30 11:58:53 -07:00 committed by Kynan Ware
parent 49d4747a81
commit a656271f26
5 changed files with 108 additions and 5 deletions

View file

@ -6,6 +6,12 @@ import (
"github.com/cli/cli/v2/internal/gh"
)
// AuthTokenRefreshable reports whether the token is stored by gh and can be
// renewed with `gh auth refresh`.
func AuthTokenRefreshable(token, src string) bool {
return token != "" && !strings.HasSuffix(src, "_TOKEN") && strings.HasPrefix(token, "gho_")
}
func AuthTokenWriteable(authCfg gh.AuthConfig, hostname string) (string, bool) {
token, src := authCfg.ActiveToken(hostname)
return src, (token == "" || !strings.HasSuffix(src, "_TOKEN"))

View file

@ -96,6 +96,9 @@ func (e authEntry) String(cs *iostreams.ColorScheme) string {
sb.WriteString(fmt.Sprintf(" - The token in %s is invalid.\n", e.TokenSource))
if authTokenWriteable(e.TokenSource) {
loginInstructions := fmt.Sprintf("gh auth login -h %s", e.Host)
if shared.AuthTokenRefreshable(e.Token, e.TokenSource) {
loginInstructions = fmt.Sprintf("gh auth refresh -h %s", e.Host)
}
logoutInstructions := fmt.Sprintf("gh auth logout -h %s -u %s", e.Host, e.Login)
sb.WriteString(fmt.Sprintf(" - To re-authenticate, run: %s\n", cs.Bold(loginInstructions)))
sb.WriteString(fmt.Sprintf(" - To forget about this account, run: %s\n", cs.Bold(logoutInstructions)))

View file

@ -184,7 +184,7 @@ func Test_statusRun(t *testing.T) {
X Failed to log in to ghe.io account monalisa-ghe (GH_CONFIG_DIR/hosts.yml)
- Active account: true
- The token in GH_CONFIG_DIR/hosts.yml is invalid.
- To re-authenticate, run: gh auth login -h ghe.io
- To re-authenticate, run: gh auth refresh -h ghe.io
- To forget about this account, run: gh auth logout -h ghe.io -u monalisa-ghe
`),
},
@ -229,7 +229,7 @@ func Test_statusRun(t *testing.T) {
X Failed to log in to ghe.io account monalisa-ghe (GH_CONFIG_DIR/hosts.yml)
- Active account: true
- The token in GH_CONFIG_DIR/hosts.yml is invalid.
- To re-authenticate, run: gh auth login -h ghe.io
- To re-authenticate, run: gh auth refresh -h ghe.io
- To forget about this account, run: gh auth logout -h ghe.io -u monalisa-ghe
`),
},
@ -447,7 +447,7 @@ func Test_statusRun(t *testing.T) {
X Failed to log in to ghe.io account monalisa-ghe (GH_CONFIG_DIR/hosts.yml)
- Active account: false
- The token in GH_CONFIG_DIR/hosts.yml is invalid.
- To re-authenticate, run: gh auth login -h ghe.io
- To re-authenticate, run: gh auth refresh -h ghe.io
- To forget about this account, run: gh auth logout -h ghe.io -u monalisa-ghe
`),
},
@ -535,7 +535,7 @@ func Test_statusRun(t *testing.T) {
X Failed to log in to ghe.io account monalisa-ghe-2 (GH_CONFIG_DIR/hosts.yml)
- Active account: true
- The token in GH_CONFIG_DIR/hosts.yml is invalid.
- To re-authenticate, run: gh auth login -h ghe.io
- To re-authenticate, run: gh auth refresh -h ghe.io
- To forget about this account, run: gh auth logout -h ghe.io -u monalisa-ghe-2
`),
},