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
- Log the full response body to see what actually came back (HTML challenge page vs JSON)
- Retry once after clearing the token cache; if it persists, re-login to get a new refresh token
- Check proxy/TLS interception: curl https://oauth2.googleapis.com/token should return JSON
- 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
- Log the raw refresh response body when this fires to detect proxy or HTML interception
- Verify direct egress to oauth2.googleapis.com when behind corporate proxies
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
- Missing tokens in response
- No refresh token found in GCP_SERVICE_ACCOUNT.
- Token refresh failed: {text}
- OAuth error: {OAuthCallbackHandler.callback_error}
- Token refresh failed: {text}
AI-assisted analysis of xtekky/gpt4free@973504e177 (2026-08-14).
Data as JSON: /api/errors/53bad9af205cdbe0.
Report an issue: GitHub.