router-for-me/CLIProxyAPI · error

failed to parse token response: %w

Error message

failed to parse token response: %w

What it means

The token exchange returned HTTP 200 but the body was not valid JSON for the expected token shape — json.Unmarshal failed. A 200 with malformed JSON usually means an intermediary (captive portal, proxy, antivirus) rewrote the response, or the endpoint returned an unexpected content type like HTML. The struct expects access_token/refresh_token/id_token fields; missing fields alone do NOT trigger this error, only syntactically invalid JSON does.

Source

Thrown at internal/auth/codex/openai_auth.go:152

		return nil, fmt.Errorf("failed to read token response: %w", err)
	}
	// log.Debugf("Token response: %s", string(body))

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("token exchange failed with status %d: %s", resp.StatusCode, string(body))
	}

	// Parse token response
	var tokenResp struct {
		AccessToken  string `json:"access_token"`
		RefreshToken string `json:"refresh_token"`
		IDToken      string `json:"id_token"`
		TokenType    string `json:"token_type"`
		ExpiresIn    int    `json:"expires_in"`
	}

	if err = json.Unmarshal(body, &tokenResp); err != nil {
		return nil, fmt.Errorf("failed to parse token response: %w", err)
	}

	// Extract account ID from ID token
	claims, err := ParseJWTToken(tokenResp.IDToken)
	if err != nil {
		log.Warnf("Failed to parse ID token: %v", err)
	}

	accountID := ""
	email := ""
	if claims != nil {
		accountID = claims.GetAccountID()
		email = claims.GetUserEmail()
	}

	// Create token data
	tokenData := CodexTokenData{
		IDToken:      tokenResp.IDToken,

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Log or inspect the raw body (temporarily re-enable the commented log.Debugf line) to see what was actually returned.
  2. Identify and bypass the intermediary — captive portal login first, or exclude the auth host from the MITM proxy.
  3. Retry from a clean network to confirm the response is provider JSON.
  4. Report upstream if the raw body is genuinely malformed provider output.

Example fix

// before
// log.Debugf("Token response: %s", string(body))

// after (temporary debugging)
log.Debugf("Token response: %s", string(body))
Defensive patterns

Strategy: try-catch

Try / catch

tok, err := auth.ExchangeCode(ctx, code)
if err != nil && strings.Contains(err.Error(), "failed to parse token response") {
    // 200 + non-JSON: almost always a portal/proxy rewriting the response
    log.Errorf("auth endpoint returned non-JSON; check captive portal/proxy: %v", err)
}

Prevention

When it happens

Trigger: Captive portal or proxy injecting an HTML page with status 200; response body truncated to invalid JSON; endpoint returning a JSON error envelope that is syntactically fine but semantically empty (that would parse, so true triggers are syntax-level); encoding mismatch after TLS interception.

Common situations: Hotel/airport Wi-Fi captive portals; MITM proxies that rewrite auth-domain responses; rare upstream incidents serving error pages with 200.

Understand the failure class

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/7147d89735472069. Report an issue: GitHub.