AlistGo/alist · error

validate token failed: empty user sub

Error message

validate token failed: empty user sub

What it means

GuangYaPan.validateToken calls GET /v1/user/me with the stored bearer token; the HTTP call succeeded (non-error status) but the JSON field userMeResp.Sub came back empty. The server accepted the request yet returned a payload without a user subject, so the token cannot be trusted for identity. This is a contract violation from the account API, not a network failure.

Source

Thrown at drivers/guangyapan/driver.go:557

	}
	return d.refreshToken(ctx)
}

func (d *GuangYaPan) validateToken(ctx context.Context) error {
	var me userMeResp
	resp, err := d.accountClient.R().
		SetContext(ctx).
		SetHeader("Authorization", "Bearer "+d.AccessToken).
		SetResult(&me).
		Get("/v1/user/me")
	if err != nil {
		return err
	}
	if resp.IsError() {
		return fmt.Errorf("validate token failed: status=%d body=%s", resp.StatusCode(), resp.String())
	}
	if strings.TrimSpace(me.Sub) == "" {
		return errors.New("validate token failed: empty user sub")
	}
	return nil
}

func (d *GuangYaPan) refreshToken(ctx context.Context) error {
	if strings.TrimSpace(d.RefreshToken) == "" {
		return errors.New("refresh_token is empty")
	}

	var out tokenResp
	resp, err := d.accountClient.R().
		SetContext(ctx).
		SetBody(map[string]any{
			"client_id":     d.ClientID,
			"grant_type":    "refresh_token",
			"refresh_token": d.RefreshToken,
		}).
		SetResult(&out).

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Log resp.String() alongside the error to inspect the actual /v1/user/me payload and confirm the field name/shape
  2. Force a token refresh: clear access_token in storage so ensureAccessToken calls refreshToken, then retry validation
  3. Re-login from scratch (SMS flow) to mint a fresh token in case the account state is stale
  4. If the payload shape changed, update userMeResp in drivers/guangyapan/types.go to match the new schema

Example fix

// before
if strings.TrimSpace(me.Sub) == "" {
	return errors.New("validate token failed: empty user sub")
}
// after (keep the guard, add response context for diagnosis)
if strings.TrimSpace(me.Sub) == "" {
	return fmt.Errorf("validate token failed: empty user sub, body=%s", resp.String())
}
Defensive patterns

Strategy: retry

Validate before calling

if strings.TrimSpace(d.AccessToken) == "" {
	if err := d.ensureAccessToken(ctx); err != nil { return err }
}

Try / catch

err := d.validateToken(ctx)
if err != nil {
	if strings.Contains(err.Error(), "empty user sub") {
		// token maps to no identity: force refresh once, then re-login if it repeats
		if rerr := d.refreshToken(ctx); rerr != nil { return rerr }
		if err = d.validateToken(ctx); err != nil { return fmt.Errorf("revalidation failed: %w", err) }
	}
}

Prevention

When it happens

Trigger: GET /v1/user/me returns 2xx with a body where the "sub" claim is missing or blank (e.g. token maps to a deleted account, an A/B response shape change on the account endpoint, or a proxy returning an empty 200). Triggers on strings.TrimSpace(me.Sub)=="" after resp.IsError() is false.

Common situations: GuangYaPan backend changed its /v1/user/me response schema; account deactivated server-side; token partially valid (accepted but identity stripped); an intermediate gateway rewriting responses.

Related errors


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