AlistGo/alist · critical
lark refresh token expired
Error message
lark refresh token expired
What it means
The Lark (Feishu) driver refuses to refresh the user access token once time.Now() passes RefreshTokenExpiresAt. Lark refresh tokens are single-line-lifetime; after expiry only a fresh OAuth user-authorization code re-initiates the flow. The check runs under tokenMu after the fast-path cache lookup missed or expired.
Source
Thrown at drivers/lark/driver.go:196
return []larkcore.RequestOptionFunc{larkcore.WithUserAccessToken(userAccessToken)}, nil
}
func (c *Lark) ensureUserAccessToken(ctx context.Context, forceRefresh bool) (string, error) {
if strings.TrimSpace(c.RefreshToken) == "" {
return strings.TrimSpace(c.UserAccessToken), nil
}
if token := strings.TrimSpace(c.UserAccessToken); !forceRefresh && token != "" && !c.userAccessTokenExpired() {
return token, nil
}
c.tokenMu.Lock()
defer c.tokenMu.Unlock()
if token := strings.TrimSpace(c.UserAccessToken); !forceRefresh && token != "" && !c.userAccessTokenExpired() {
return token, nil
}
if c.RefreshTokenExpiresAt > 0 && time.Now().After(time.Unix(c.RefreshTokenExpiresAt, 0)) {
return "", errors.New("lark refresh token expired")
}
resp, err := c.client.Ext.Authen.RefreshAuthenAccessToken(ctx,
larkext.NewRefreshAuthenAccessTokenReqBuilder().
Body(larkext.NewRefreshAuthenAccessTokenReqBodyBuilder().
GrantType(larkext.GrantTypeRefreshCode).
RefreshToken(strings.TrimSpace(c.RefreshToken)).
Build()).
Build())
if err != nil {
return "", err
}
if !resp.Success() {
return "", errors.New(resp.Error())
}
if resp.Data == nil || resp.Data.AccessToken == "" {
return "", errors.New("lark refresh token response missing access token")
}View on GitHub (pinned to 843d9dc814)
Solutions
- Re-authorize: generate a new user access/refresh token pair via the Feishu OAuth flow and update the storage
- Verify the alist host clock (NTP) — skew can fake expiry
- Schedule a lightweight periodic Drive call (e.g. a List) to keep tokens refreshed before they lapse
Defensive patterns
Strategy: validation
Validate before calling
if c.RefreshTokenExpiresAt > 0 && time.Now().After(time.Unix(c.RefreshTokenExpiresAt, 0)) {
return errors.New("lark refresh token already expired: re-authorize the storage")
} Type guard
func (c *Lark) refreshTokenAlive() bool {
return c.RefreshTokenExpiresAt <= 0 || time.Now().Before(time.Unix(c.RefreshTokenExpiresAt, 0))
} Prevention
- Keep storages warm with a scheduled lightweight call so tokens refresh before expiry
- Monitor RefreshTokenExpiresAt and alert ahead of expiry; re-auth is manual once it lapses
- Sync host clocks via NTP to avoid false expiry
When it happens
Trigger: getUserAccessToken with an expired cached token AND c.RefreshTokenExpiresAt>0 AND now after that unix time. Happens on any Drive call after the storage has been idle longer than the refresh-token lifetime (typically tens of days) since last save.
Common situations: Storage unused for weeks/months then revisited; server clock skew on the alist host making local 'now' past expiry; RefreshExpiresIn previously saved short.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- lark refresh token response missing access token
- failed to get download link
- lark download requires web proxy
- lark tenant access token is empty
- lark file token not found
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/512010fc5652b9a3.
Report an issue: GitHub.