{"record":{"id":"3edeb74b2120e616","repo":"HKUDS/DeepTutor","slug":"github-copilot-token-exchange-returned-no-token","errorCode":null,"errorMessage":"GitHub Copilot token exchange returned no token.","messagePattern":"GitHub Copilot token exchange returned no token\\.","errorType":"error_code","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/llm/provider_core/github_copilot_provider.py","lineNumber":88,"sourceCode":"        async with httpx.AsyncClient(\n            timeout=timeout, follow_redirects=True, trust_env=True\n        ) as client:\n            response = await client.get(\n                DEFAULT_COPILOT_TOKEN_URL,\n                headers={\n                    \"Authorization\": f\"token {github_token}\",\n                    \"Accept\": \"application/json\",\n                    \"User-Agent\": USER_AGENT,\n                    \"Editor-Version\": EDITOR_VERSION,\n                    \"Editor-Plugin-Version\": EDITOR_PLUGIN_VERSION,\n                },\n            )\n            response.raise_for_status()\n            payload = response.json()\n\n        token = payload.get(\"token\")\n        if not token:\n            raise RuntimeError(\"GitHub Copilot token exchange returned no token.\")\n        expires_at = payload.get(\"expires_at\")\n        if isinstance(expires_at, (int, float)):\n            self._copilot_expires_at = float(expires_at)\n        else:\n            refresh_in = payload.get(\"refresh_in\") or 1500\n            self._copilot_expires_at = time.time() + int(refresh_in)\n        self._copilot_access_token = str(token)\n        return self._copilot_access_token\n\n    async def _ensure_api_key(self) -> None:\n        now = time.time()\n        if self._copilot_access_token and now < self._copilot_expires_at - _EXPIRY_SKEW_SECONDS:\n            self.api_key = self._copilot_access_token\n            self._client.api_key = self._copilot_access_token\n            return\n\n        await self._try_existing_local_auth()\n","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/llm/provider_core/github_copilot_provider.py#L70-L106","documentation":"After POSTing to the Copilot token-exchange endpoint, the JSON payload lacked a 'token' field, so the provider cannot authenticate subsequent API calls and raises RuntimeError.","triggerScenarios":"The exchange endpoint returns 200 but a body without 'token' (or token=null/empty) — API contract change, rate limiting response, or the GitHub token being invalid such that the exchange returns an error-shaped payload with 200.","commonSituations":"GitHub/Copilot API changes; expired or revoked GitHub OAuth token producing a degenerate response; proxy or firewall returning an HTML interception page parsed as JSON.","solutions":["Log in again (`deeptutor provider login github-copilot`) to refresh the underlying GitHub token.","Check network/proxy isn't intercepting the exchange request.","Update DeepTutor — the exchange contract may have changed."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    resp = await provider.chat(messages)\nexcept RuntimeError as e:\n    if 'token exchange' in str(e):\n        relogin_and_retry_once()\n    else:\n        raise","preventionTips":["Treat exchange anomalies as stale-auth signals and auto-trigger re-login","Pin known-good DeepTutor versions to track Copilot API changes","Log the exchange payload keys (never values) when debugging"],"tags":["auth","github-copilot","token-exchange"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}