xtekky/gpt4free · error · RuntimeError

Token refresh failed: {text}

Error message

Token refresh failed: {text}

What it means

The POST to Google's OAuth refresh endpoint (OAUTH_REFRESH_URL with grant_type=refresh_token) returned a non-200 status; the response body is embedded in the message. Typical bodies are invalid_grant (refresh token expired, revoked, or issued to a different client_id), invalid_client (wrong client_id/client_secret pair), or a redirect_uri mismatch.

Source

Thrown at g4f/Provider/needs_auth/GeminiCLI.py:416

        await self._refresh_and_cache_token(refresh_token)

    async def _refresh_and_cache_token(self, refresh_token: str) -> None:
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        data = {
            "client_id": self.OAUTH_CLIENT_ID,
            "client_secret": self.OAUTH_CLIENT_SECRET,
            "refresh_token": refresh_token,
            "grant_type": "refresh_token",
        }

        async with aiohttp.ClientSession() as session:
            async with session.post(
                self.OAUTH_REFRESH_URL, data=data, headers=headers
            ) as resp:
                if resp.status != 200:
                    text = await resp.text()
                    raise RuntimeError(f"Token refresh failed: {text}")
                resp_data = await resp.json()
                access_token = resp_data.get("access_token")
                expires_in = resp_data.get("expires_in", 3600)  # seconds

                if not access_token:
                    raise RuntimeError("No access_token in refresh response.")

                self._access_token = access_token
                self._expiry = time.time() + expires_in

                expiry_date_ms = int(self._expiry * 1000)  # milliseconds

                await self._cache_token(access_token, expiry_date_ms)

    async def _cache_token(self, access_token: str, expiry_date: int) -> None:
        # Cache token in KV store or fallback to memory cache
        token_data = {
            "access_token": access_token,

View on GitHub (pinned to 973504e177)

Solutions

  1. Read the embedded response body: 'invalid_grant' means re-login to mint a new refresh token; 'invalid_client' means the embedded client_id/secret are wrong for your token
  2. Re-run the GeminiCLI login flow and replace GCP_SERVICE_ACCOUNT with the fresh token JSON
  3. Upgrade g4f to the latest version so OAUTH_CLIENT_ID/OAUTH_CLIENT_SECRET match the ones the login flow uses
  4. If behind a proxy, ensure POSTs to oauth2.googleapis.com are not modified (some MITM proxies break form encoding)

Example fix

try:
    await provider.generate_async(...)
except RuntimeError as e:
    if "Token refresh failed" in str(e) and "invalid_grant" in str(e):
        await GeminiCLI.login()  # mint a fresh refresh token
Defensive patterns

Strategy: try-catch

Try / catch

try:
    await provider.generate_async(...)
except RuntimeError as e:
    msg = str(e)
    if "Token refresh failed" in msg:
        if "invalid_grant" in msg:
            await GeminiCLI.login()          # refresh token dead: re-auth
        else:
            await asyncio.sleep(5)           # transient: retry once
            await provider.generate_async(...)

Prevention

When it happens

Trigger: Any GeminiCLI request after the access token expires, when the refresh token is stale (Google refresh tokens for this OAuth client get invalidated), or when OAUTH_CLIENT_ID/OAUTH_CLIENT_SECRET constants no longer match the token that was issued.

Common situations: Refresh token revoked via the Google account security page or by issuing too many new tokens; g4f version bump changed the embedded OAuth client credentials so old refresh tokens no longer match; clock skew on the host; network proxy stripping the form-encoded POST.

Related errors


AI-assisted analysis of xtekky/gpt4free@973504e177 (2026-08-14). Data as JSON: /api/errors/2a55c8983dacd77a. Report an issue: GitHub.