{"record":{"id":"53bad9af205cdbe0","repo":"xtekky/gpt4free","slug":"no-access-token-in-refresh-response-53bad9","errorCode":null,"errorMessage":"No access_token in refresh response.","messagePattern":"No access_token in refresh response\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"g4f/Provider/needs_auth/GeminiCLI.py","lineNumber":422,"sourceCode":"            \"client_id\": self.OAUTH_CLIENT_ID,\n            \"client_secret\": self.OAUTH_CLIENT_SECRET,\n            \"refresh_token\": refresh_token,\n            \"grant_type\": \"refresh_token\",\n        }\n\n        async with aiohttp.ClientSession() as session:\n            async with session.post(\n                self.OAUTH_REFRESH_URL, data=data, headers=headers\n            ) as resp:\n                if resp.status != 200:\n                    text = await resp.text()\n                    raise RuntimeError(f\"Token refresh failed: {text}\")\n                resp_data = await resp.json()\n                access_token = resp_data.get(\"access_token\")\n                expires_in = resp_data.get(\"expires_in\", 3600)  # seconds\n\n                if not access_token:\n                    raise RuntimeError(\"No access_token in refresh response.\")\n\n                self._access_token = access_token\n                self._expiry = time.time() + expires_in\n\n                expiry_date_ms = int(self._expiry * 1000)  # milliseconds\n\n                await self._cache_token(access_token, expiry_date_ms)\n\n    async def _cache_token(self, access_token: str, expiry_date: int) -> None:\n        # Cache token in KV store or fallback to memory cache\n        token_data = {\n            \"access_token\": access_token,\n            \"expiry_date\": expiry_date,\n            \"cached_at\": int(time.time() * 1000),  # ms\n        }\n        self._token_cache[self.KV_TOKEN_KEY] = token_data\n\n    async def _get_cached_token(self) -> Optional[Dict[str, Any]]:","sourceCodeStart":404,"sourceCodeEnd":440,"githubUrl":"https://github.com/xtekky/gpt4free/blob/973504e1770928ed5fb82f43da528f441ad9ddc3/g4f/Provider/needs_auth/GeminiCLI.py#L404-L440","documentation":"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.","triggerScenarios":"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.","commonSituations":"Transparent proxy or captive portal returning a 200 HTML page; a rare Google-side transient; response schema change after an endpoint update.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for attempt in range(2):\n    try:\n        return await auth_manager.get_access_token()\n    except RuntimeError as e:\n        if \"No access_token in refresh response\" in str(e) and attempt == 0:\n            await auth_manager.clear_token_cache()\n            continue\n        raise","preventionTips":["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"],"tags":["auth","oauth","gemini","network","response-parsing"],"backgroundTag":null,"analyzedSha":"973504e1770928ed5fb82f43da528f441ad9ddc3","analyzedAt":"2026-08-14T23:45:32.408Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}