{"record":{"id":"21e1c7ba34e2ea29","repo":"AlistGo/alist","slug":"failed-to-refresh-token-refresh-token-is-empty","errorCode":null,"errorMessage":"failed to refresh token: refresh token is empty","messagePattern":"failed to refresh token: refresh token is empty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"drivers/aliyundrive/util.go","lineNumber":77,"sourceCode":"\nfunc (d *AliDrive) refreshToken() error {\n\turl := \"https://auth.alipan.com/v2/account/token\"\n\tvar resp base.TokenResp\n\tvar e RespErr\n\t_, err := base.RestyClient.R().\n\t\t//ForceContentType(\"application/json\").\n\t\tSetBody(base.Json{\"refresh_token\": d.RefreshToken, \"grant_type\": \"refresh_token\"}).\n\t\tSetResult(&resp).\n\t\tSetError(&e).\n\t\tPost(url)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif e.Code != \"\" {\n\t\treturn fmt.Errorf(\"failed to refresh token: %s\", e.Message)\n\t}\n\tif resp.RefreshToken == \"\" {\n\t\treturn errors.New(\"failed to refresh token: refresh token is empty\")\n\t}\n\td.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken\n\top.MustSaveDriverStorage(d)\n\treturn nil\n}\n\nfunc (d *AliDrive) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error, RespErr) {\n\treq := base.RestyClient.R()\n\tstate, ok := global.Load(d.UserID)\n\tif !ok {\n\t\tif url == \"https://api.alipan.com/v2/user/get\" {\n\t\t\tstate = &State{}\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"can't load user state, user_id: %s\", d.UserID), RespErr{}\n\t\t}\n\t}\n\treq.SetHeaders(map[string]string{\n\t\t\"Authorization\": \"Bearer\\t\" + d.AccessToken,","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/aliyundrive/util.go#L59-L95","documentation":"Thrown by the legacy AliDrive (aliyundrive personal) token refresh when the open.aliyundrive.com OAuth token endpoint returns HTTP success with no error code but an EMPTY refresh_token in the response body. Because the driver rotates refresh tokens (each may be single-use), an empty new refresh token would brick the storage, so it aborts before overwriting stored credentials.","triggerScenarios":"POST to the token endpoint with grant_type=refresh_token succeeds (no e.Code), but resp.RefreshToken == \"\" — server quirk, truncated body, rate limiting, or an account-side issue returning a partial payload.","commonSituations":"Long-running aliyundrive mounts whose stored refresh token was already consumed by another instance (two Alist deployments sharing one token); API endpoint behavior changes after alipan.com migration; network middleboxes mangling response bodies; token revoked from the Aliyun console.","solutions":["Ensure only ONE deployment/refresh-token holder uses this refresh token — concurrent refreshers consume each other's rotated tokens; disable the duplicate instance","Re-obtain a fresh refresh_token (re-run the QR/token acquisition for the driver) and update the storage config","Check e.Code/response body by enabling request logging to confirm whether the endpoint actually returned an error payload the code missed","If the account was migrated to the Open Platform, switch to the aliyundrive_open driver instead of the legacy one"],"exampleFix":"// before\nif resp.RefreshToken == \"\" {\n    return errors.New(\"failed to refresh token: refresh token is empty\")\n}\n\n// after — surface the raw payload for diagnosis\nif resp.RefreshToken == \"\" {\n    return fmt.Errorf(\"failed to refresh token: refresh token is empty, resp: %s\", res.String())\n}","handlingStrategy":"validation","validationCode":"// Before mounting, sanity-check the configured refresh token\nif d.RefreshToken == \"\" {\n    return errors.New(\"refresh_token missing; re-authorize the storage\")\n}\n// ensure no other instance refreshes with the same token\n// (single owner per refresh token)","typeGuard":"null","tryCatchPattern":"if err := d.refreshToken(); err != nil {\n    if strings.Contains(err.Error(), \"refresh token is empty\") {\n        // credentials are unusable: stop retrying and prompt re-authorization\n    }\n    return err\n}","preventionTips":["Run exactly one refresher per refresh token — rotated tokens are single-use; shared tokens burn each other","Store refresh tokens durably and only overwrite after verifying the replacement is non-empty","Prefer the aliyundrive_open driver for new setups; the legacy API is deprecated"],"tags":["aliyundrive","oauth","refresh-token","auth"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}