AlistGo/alist · critical
failed to refresh token: sub not match
Error message
failed to refresh token: sub not match
What it means
Thrown by aliyundrive_open's token refresh when the freshly issued refresh token carries a `sub` (subject/user identifier) JWT claim that differs from the one in the currently stored refresh token. The Open Platform rotates refresh tokens; if the new token belongs to a different user/account, persisting it would silently switch the storage to another account, so the driver aborts.
Source
Thrown at drivers/aliyundrive_open/util.go:151
}
}
if e.Code != "" {
return "", "", fmt.Errorf("failed to refresh token: %s", e.Message)
}
refresh, access := utils.Json.Get(res.Body(), "refresh_token").ToString(), utils.Json.Get(res.Body(), "access_token").ToString()
if refresh == "" {
return "", "", fmt.Errorf("failed to refresh token: refresh token is empty, resp: %s", res.String())
}
curSub, err := getSub(d.RefreshToken)
if err != nil {
return "", "", err
}
newSub, err := getSub(refresh)
if err != nil {
return "", "", err
}
if curSub != newSub {
return "", "", errors.New("failed to refresh token: sub not match")
}
return refresh, access, nil
}
func getSub(token string) (string, error) {
segments := strings.Split(token, ".")
if len(segments) != 3 {
return "", errors.New("not a jwt token because of invalid segments")
}
bs, err := base64.RawStdEncoding.DecodeString(segments[1])
if err != nil {
return "", errors.New("failed to decode jwt token")
}
return utils.Json.Get(bs, "sub").ToString(), nil
}
func (d *AliyundriveOpen) refreshToken(ctx context.Context) error {
if d.ref != nil {View on GitHub (pinned to 843d9dc814)
Solutions
- Redo the full authorization flow (QR login) for THIS storage so the refresh token matches the account tied to its client_id — do not hand-mix tokens
- Verify the refresh_token in the storage config was issued for the same Aliyun account and the same Open-Platform app (client_id) as the one refreshing it
- If the account itself changed (family/enterprise migration), wipe both tokens and re-authorize from scratch to re-baseline the `sub`
- Never paste refresh tokens between different driver instances or accounts
Example fix
# before — token pasted from another account/client refresh_token: eyJhbGciOi...subA... # after — re-run in-browser QR authorization for this storage, # then keep the freshly issued matching pair together: refresh_token: eyJhbGciOi...subB... # same sub as client_id owner access_token: eyJhbGciOi...subB...
Defensive patterns
Strategy: validation
Validate before calling
// Before saving config, verify the token's sub matches the intended account
sub, err := getSub(proposedRefreshToken)
if err != nil {
return fmt.Errorf("token unreadable: %w", err)
}
if existingSub != "" && sub != existingSub {
return errors.New("refresh token belongs to a different account (sub mismatch)")
} Type guard
// Go
func isSubMismatch(err error) bool {
return err != nil && strings.Contains(err.Error(), "sub not match")
} Try / catch
if _, _, err := d.refreshToken(); isSubMismatch(err) {
// halt automated retries; re-run the full QR authorization for this storage
return err
} Prevention
- Never paste refresh tokens between accounts, apps, or driver instances
- Complete the whole auth flow (QR) per storage so tokens and client_id stay paired
- If an account migrates (family/enterprise), wipe both tokens and re-authorize from scratch
When it happens
Trigger: refreshToken() succeeds, then getSub() decodes the `sub` claim of both the old and new JWTs and they differ — e.g. the refresh_token in config was issued for user A while the client_id/QR-login context belongs to user B, or a pasted token was later re-issued under a family/enterprise master account.
Common situations: Copy-pasting a refresh token from one Aliyun account into a storage configured with another account's client; account merges/family plan conversions changing the subject ID; mixing tokens between the qr endpoint and a different app's client_id; manually editing the refresh_token field after re-login elsewhere.
Related errors
- not a jwt token because of invalid segments
- failed to decode jwt token
- token is invalidated
- that's not even a token
- token is expired
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/bfeee34e08f16e12.
Report an issue: GitHub.