{"record":{"id":"b70e84978e1470e1","repo":"AlistGo/alist","slug":"lark-refresh-token-response-missing-access-token","errorCode":null,"errorMessage":"lark refresh token response missing access token","messagePattern":"lark refresh token response missing access token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/lark/driver.go","lineNumber":213,"sourceCode":"\tif c.RefreshTokenExpiresAt > 0 && time.Now().After(time.Unix(c.RefreshTokenExpiresAt, 0)) {\n\t\treturn \"\", errors.New(\"lark refresh token expired\")\n\t}\n\n\tresp, err := c.client.Ext.Authen.RefreshAuthenAccessToken(ctx,\n\t\tlarkext.NewRefreshAuthenAccessTokenReqBuilder().\n\t\t\tBody(larkext.NewRefreshAuthenAccessTokenReqBodyBuilder().\n\t\t\t\tGrantType(larkext.GrantTypeRefreshCode).\n\t\t\t\tRefreshToken(strings.TrimSpace(c.RefreshToken)).\n\t\t\t\tBuild()).\n\t\t\tBuild())\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !resp.Success() {\n\t\treturn \"\", errors.New(resp.Error())\n\t}\n\tif resp.Data == nil || resp.Data.AccessToken == \"\" {\n\t\treturn \"\", errors.New(\"lark refresh token response missing access token\")\n\t}\n\n\tnow := time.Now()\n\tc.UserAccessToken = resp.Data.AccessToken\n\tc.UserAccessTokenExpiresAt = now.Add(time.Duration(resp.Data.ExpiresIn) * time.Second).Unix()\n\tif resp.Data.RefreshToken != \"\" {\n\t\tc.RefreshToken = resp.Data.RefreshToken\n\t}\n\tif resp.Data.RefreshExpiresIn > 0 {\n\t\tc.RefreshTokenExpiresAt = now.Add(time.Duration(resp.Data.RefreshExpiresIn) * time.Second).Unix()\n\t}\n\top.MustSaveDriverStorage(c)\n\n\treturn c.UserAccessToken, nil\n}\n\nfunc (c *Lark) forceRefreshUserAccessToken(ctx context.Context) error {\n\tif strings.TrimSpace(c.RefreshToken) == \"\" {","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/lark/driver.go#L195-L231","documentation":"The Lark refresh endpoint answered HTTP+code success but resp.Data was nil or AccessToken empty. The SDK call succeeded and resp.Success() passed, yet no usable credential came back — a malformed/unexpected body. The driver refuses to overwrite stored tokens with emptiness (which would lock the account out), so it aborts before the MustSaveDriverStorage in the lines below.","triggerScenarios":"RefreshAuthenAccessToken returns a 2xx whose data lacks access_token: expired/revoked refresh_token that Feishu answers with an odd envelope, gateway interference, or SDK deserialization drift after an API change.","commonSituations":"Refresh token actually dead but error surfaced as success-shaped; lark SDK version mismatch; tenant/app permissions changed so the endpoint no longer returns a token.","solutions":["Log resp.Error() and the raw response to see which envelope shape came back","If the refresh token is dead, redo the OAuth authorization to mint a new pair","Update alist / the vendored lark SDK in case the response schema changed","Confirm the app still has the required drive scopes enabled in the Feishu console"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if strings.TrimSpace(c.RefreshToken) == \"\" {\n\treturn \"\", errors.New(\"cannot refresh: refresh_token empty\")\n}","typeGuard":null,"tryCatchPattern":"tok, err := c.getUserAccessToken(ctx, false)\nif err != nil && strings.Contains(err.Error(), \"missing access token\") {\n\t// success-shaped failure usually means dead refresh token: force re-auth, don't loop\n\treturn \"\", fmt.Errorf(\"lark token refresh unusable, re-authorize storage: %w\", err)\n}","preventionTips":["Never retry this error in a loop — the stored token pair is not updated on this path, so it repeats","Log the raw refresh response once to distinguish revoked token from schema drift"],"tags":["lark","feishu","oauth","api-contract","driver"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}