router-for-me/CLIProxyAPI · error

refresh response did not include access_token

Error message

refresh response did not include access_token

What it means

After successfully calling RefreshTokensWithRetry in cmd/fetch_codex_models/main.go, the refresh response is inspected for an access token. If `tokenData.AccessToken` is empty/whitespace after trimming, the tool aborts with `refresh response did not include access_token`: the OAuth token endpoint answered 200 but its payload did not contain the expected field, so persisting it would produce an unusable credential.

Source

Thrown at cmd/fetch_codex_models/main.go:203

			return accessToken, false, nil
		}
	}

	refreshToken := metaStringValue(auth.Metadata, "refresh_token")
	if refreshToken == "" {
		if accessToken != "" {
			return accessToken, false, nil
		}
		return "", false, fmt.Errorf("missing access_token and refresh_token")
	}

	svc := codexauth.NewCodexAuthWithProxyURL(nil, auth.ProxyURL)
	tokenData, errRefresh := svc.RefreshTokensWithRetry(ctx, refreshToken, 3)
	if errRefresh != nil {
		return "", false, errRefresh
	}
	if strings.TrimSpace(tokenData.AccessToken) == "" {
		return "", false, fmt.Errorf("refresh response did not include access_token")
	}

	if auth.Metadata == nil {
		auth.Metadata = make(map[string]any)
	}
	auth.Metadata["id_token"] = tokenData.IDToken
	auth.Metadata["access_token"] = tokenData.AccessToken
	if tokenData.RefreshToken != "" {
		auth.Metadata["refresh_token"] = tokenData.RefreshToken
	}
	if tokenData.AccountID != "" {
		auth.Metadata["account_id"] = tokenData.AccountID
	}
	if tokenData.Email != "" {
		auth.Metadata["email"] = tokenData.Email
	}
	auth.Metadata["expired"] = tokenData.Expire
	auth.Metadata["type"] = "codex"

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Dump the raw refresh response (temporarily add logging) to see what the endpoint actually returned.
  2. Update the Codex client version string used for the refresh request to match the current Codex CLI.
  3. Remove intercepting proxies or configure proxy settings so the OAuth response is untouched.
  4. Re-run full OAuth login to mint a fresh refresh token, in case the old one triggers a degraded response.
  5. Check CLIProxyAPI issues/releases — an upstream API change likely needs a code fix in the codex auth package.
Defensive patterns

Strategy: fallback

Type guard

func isMissingAccessTokenError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "refresh response did not include access_token")
}

Try / catch

token, refreshed, err := ensureToken(ctx, auth)
if isMissingAccessTokenError(err) {
    // refresh token likely degraded: fall back to full re-login
    err = reloginAndReplaceAuthFile(ctx, auth)
}

Prevention

When it happens

Trigger: OpenAI/Codex token endpoint returns a 200 body lacking `access_token` (e.g. only an id_token), the response shape changes after an API update, or a proxy in front mangles the JSON. Retry logic already ran (3 attempts) before this check, so it is not transient throttling.

Common situations: Upstream auth API contract change after a Codex client-version bump; an intercepting corporate proxy returning HTML/empty JSON; clock/protocol skew causing the endpoint to return an error body with 200; rare account states where refresh yields no access token.

Related errors


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