xtekky/gpt4free · error · RuntimeError

No access_token in refresh response.

Error message

No access_token in refresh response.

What it means

The refresh endpoint answered HTTP 200 but the JSON body had no access_token field. Google's token endpoint always returns access_token on success, so this indicates a malformed or intercepted response (captive portal, HTML error page parsed as JSON, or an API surface change), not a normal OAuth condition.

Source

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

            "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,
            "expiry_date": expiry_date,
            "cached_at": int(time.time() * 1000),  # ms
        }
        self._token_cache[self.KV_TOKEN_KEY] = token_data

    async def _get_cached_token(self) -> Optional[Dict[str, Any]]:

View on GitHub (pinned to 973504e177)

Solutions

  1. Log the full response body to see what actually came back (HTML challenge page vs JSON)
  2. Retry once after clearing the token cache; if it persists, re-login to get a new refresh token
  3. Check proxy/TLS interception: curl https://oauth2.googleapis.com/token should return JSON
  4. Update g4f in case the endpoint or response handling changed upstream
Defensive patterns

Strategy: retry

Try / catch

for attempt in range(2):
    try:
        return await auth_manager.get_access_token()
    except RuntimeError as e:
        if "No access_token in refresh response" in str(e) and attempt == 0:
            await auth_manager.clear_token_cache()
            continue
        raise

Prevention

When it happens

Trigger: session.post to OAUTH_REFRESH_URL returns 200 with a body missing the access_token key; resp.json() succeeds but resp_data.get('access_token') is None or empty.

Common situations: Transparent proxy or captive portal returning a 200 HTML page; a rare Google-side transient; response schema change after an endpoint update.

Related errors


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