sipeed/picoclaw · error

no access token in response

Error message

no access token in response

What it means

parseTokenResponse (pkg/auth/oauth.go:583) decoded the body as JSON but access_token was missing or empty. The exchange technically 'succeeded' at the JSON level, yet there is no usable credential. Providers usually omit access_token only in error payloads or partial responses.

Source

Thrown at pkg/auth/oauth.go:583

		return nil, fmt.Errorf("token exchange failed: %s", string(body))
	}

	return parseTokenResponse(body, provider)
}

func parseTokenResponse(body []byte, provider string) (*AuthCredential, error) {
	var tokenResp struct {
		AccessToken  string `json:"access_token"`
		RefreshToken string `json:"refresh_token"`
		ExpiresIn    int    `json:"expires_in"`
		IDToken      string `json:"id_token"`
	}
	if err := json.Unmarshal(body, &tokenResp); err != nil {
		return nil, fmt.Errorf("parsing token response: %w", err)
	}

	if tokenResp.AccessToken == "" {
		return nil, fmt.Errorf("no access token in response")
	}

	var expiresAt time.Time
	if tokenResp.ExpiresIn > 0 {
		expiresAt = time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)
	}

	cred := &AuthCredential{
		AccessToken:  tokenResp.AccessToken,
		RefreshToken: tokenResp.RefreshToken,
		ExpiresAt:    expiresAt,
		Provider:     provider,
		AuthMethod:   "oauth",
	}

	// Recent OpenAI OAuth responses may only include chatgpt_account_id in id_token claims.
	if id := extractAccountID(tokenResp.IDToken); id != "" {
		cred.AccountID = id

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Log the (redacted) body to see exactly which fields the provider returned
  2. If it is an error envelope despite 200, treat the flow as failed and restart login; check the provider's docs for the payload shape
  3. If the field was renamed (e.g. accessToken), update the json tag in parseTokenResponse
  4. For id_token-only responses, decide whether validating id_token suffices for your flow

Example fix

// before
if tokenResp.AccessToken == "" {
	return nil, fmt.Errorf("no access token in response")
}

// after (name the fields that were present)
if tokenResp.AccessToken == "" {
	return nil, fmt.Errorf("no access token in response (fields present: %s)", strings.Join(presentFields(body), ", "))
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Confirm the key exists and is non-empty before trusting the flow
var probe struct {
	AccessToken string `json:"access_token"`
}
if err := json.Unmarshal(body, &probe); err == nil && strings.TrimSpace(probe.AccessToken) == "" {
	return fmt.Errorf("provider returned no access_token")
}

Type guard

func isNoAccessTokenError(err error) bool {
	return err != nil && strings.Contains(err.Error(), "no access token in response")
}

Try / catch

cred, err := auth.ExchangeCodeForTokens(cfg, code, verifier, redirectURI)
if err != nil && isNoAccessTokenError(err) {
	// 200-without-token usually means provider error envelope: restart login
	return reloginFlow(cfg)
}

Prevention

When it happens

Trigger: Token endpoint returns 200 with an error object like {"error":"..."} or a payload containing only id_token/refresh_token; an API change renaming access_token; a proxy rewriting the response.

Common situations: Provider 200-status error envelopes (nonstandard but seen in the wild); Google-style flows returning only an id_token for some grant types; partially stubbed test servers; field renaming after a provider API version bump.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/11d88b29c1a0bc38. Report an issue: GitHub.