AlistGo/alist · error

lark refresh token response missing access token

Error message

lark refresh token response missing access token

What it means

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.

Source

Thrown at drivers/lark/driver.go:213

	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")
	}

	now := time.Now()
	c.UserAccessToken = resp.Data.AccessToken
	c.UserAccessTokenExpiresAt = now.Add(time.Duration(resp.Data.ExpiresIn) * time.Second).Unix()
	if resp.Data.RefreshToken != "" {
		c.RefreshToken = resp.Data.RefreshToken
	}
	if resp.Data.RefreshExpiresIn > 0 {
		c.RefreshTokenExpiresAt = now.Add(time.Duration(resp.Data.RefreshExpiresIn) * time.Second).Unix()
	}
	op.MustSaveDriverStorage(c)

	return c.UserAccessToken, nil
}

func (c *Lark) forceRefreshUserAccessToken(ctx context.Context) error {
	if strings.TrimSpace(c.RefreshToken) == "" {

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Log resp.Error() and the raw response to see which envelope shape came back
  2. If the refresh token is dead, redo the OAuth authorization to mint a new pair
  3. Update alist / the vendored lark SDK in case the response schema changed
  4. Confirm the app still has the required drive scopes enabled in the Feishu console
Defensive patterns

Strategy: try-catch

Validate before calling

if strings.TrimSpace(c.RefreshToken) == "" {
	return "", errors.New("cannot refresh: refresh_token empty")
}

Try / catch

tok, err := c.getUserAccessToken(ctx, false)
if err != nil && strings.Contains(err.Error(), "missing access token") {
	// success-shaped failure usually means dead refresh token: force re-auth, don't loop
	return "", fmt.Errorf("lark token refresh unusable, re-authorize storage: %w", err)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/b70e84978e1470e1. Report an issue: GitHub.