{"record":{"id":"bee4d8af425c60a2","repo":"AlistGo/alist","slug":"refresh-token-failed-s","errorCode":null,"errorMessage":"refresh token failed: %s","messagePattern":"refresh token failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/guangyapan/driver.go","lineNumber":591,"sourceCode":"\t\t\t\"refresh_token\": d.RefreshToken,\n\t\t}).\n\t\tSetResult(&out).\n\t\tPost(\"/v1/auth/token\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif resp.IsError() || out.Error != \"\" || strings.TrimSpace(out.AccessToken) == \"\" {\n\t\terrMsg := strings.TrimSpace(out.ErrorDesc)\n\t\tif errMsg == \"\" {\n\t\t\terrMsg = strings.TrimSpace(out.Error)\n\t\t}\n\t\tif errMsg == \"\" {\n\t\t\terrMsg = strings.TrimSpace(resp.String())\n\t\t}\n\t\tif errMsg == \"\" {\n\t\t\terrMsg = fmt.Sprintf(\"status=%d\", resp.StatusCode())\n\t\t}\n\t\treturn fmt.Errorf(\"refresh token failed: %s\", errMsg)\n\t}\n\n\td.AccessToken = strings.TrimSpace(out.AccessToken)\n\tif strings.TrimSpace(out.RefreshToken) != \"\" {\n\t\td.RefreshToken = strings.TrimSpace(out.RefreshToken)\n\t}\n\top.MustSaveDriverStorage(d)\n\treturn nil\n}\n\nfunc (d *GuangYaPan) canSMSLogin() bool {\n\treturn d.PhoneNumber != \"\" && d.VerifyCode != \"\"\n}\n\nfunc (d *GuangYaPan) loginBySMSCode(ctx context.Context) error {\n\tverificationID := strings.TrimSpace(d.VerificationID)\n\tif verificationID == \"\" {\n\t\tvar err error","sourceCodeStart":573,"sourceCodeEnd":609,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/guangyapan/driver.go#L573-L609","documentation":"refreshToken posts to the token endpoint and throws when the response is an HTTP error, carries an OAuth-style error field, or the returned AccessToken is empty. The message picks the most informative detail available: ErrorDesc, then Error, then the raw body, then just the status code. Because tokens are only persisted after success, storage state is preserved.","triggerScenarios":"postAPI hits 401/403 and calls refreshToken with an expired/revoked RefreshToken; the provider rejects the grant (invalid_grant); or a successful-status response omits access_token.","commonSituations":"Storage unused for a long time so the refresh token rotated/expired; refresh token reused after another client already rotated it; account service temporarily failing.","solutions":["If errMsg says invalid_grant/expired: re-do SMS login (fill phone_number and a fresh verify_code, save storage).","Retry once after a short delay for transient 5xx failures.","Confirm the storage's refresh_token field was not manually edited or truncated."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := op.Retry(2, time.Second, func() error {\n    return d.refreshToken(ctx)\n}); err != nil {\n    if strings.Contains(err.Error(), \"invalid_grant\") || strings.Contains(err.Error(), \"expired\") {\n        // unrecoverable: require SMS re-login\n        return errors.New(\"please re-login via SMS (fill verify_code)\")\n    }\n    return err\n}","preventionTips":["Use the storage regularly so refresh tokens do not sit past rotation windows.","Never manually edit stored tokens.","Distinguish permanent (invalid_grant) from transient (5xx) refresh failures before re-login prompts."],"tags":["auth","refresh-token","oauth","guangyapan"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}