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
- 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
- Re-run the GeminiCLI login flow and replace GCP_SERVICE_ACCOUNT with the fresh token JSON
- Upgrade g4f to the latest version so OAUTH_CLIENT_ID/OAUTH_CLIENT_SECRET match the ones the login flow uses
- 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
- Treat invalid_grant as terminal for the stored token and re-login immediately
- Log the embedded response body — it names the exact OAuth error
- Keep g4f updated so the embedded OAuth client matches the login flow
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
- No refresh token found in GCP_SERVICE_ACCOUNT.
- No access_token in refresh response.
- Token exchange failed: {error_text}
- Response {response.status}: {message}
- OAuth error: {OAuthCallbackHandler.callback_error}
AI-assisted analysis of xtekky/gpt4free@973504e177 (2026-08-14).
Data as JSON: /api/errors/2a55c8983dacd77a.
Report an issue: GitHub.