Aider-AI/aider · error · GitHubCopilotTokenError
GitHub Copilot API request failed (Status: {res.status_code}
Error message
GitHub Copilot API request failed (Status: {res.status_code})
URL: {url}
Headers: {json.dumps(safe_headers, indent=2)}
JSON: {res.text} What it means
The GitHub Copilot token-exchange GET to https://api.github.com/copilot_internal/v2/token returned a non-200 status, and aider raises its locally-defined GitHubCopilotTokenError with status code, URL, safe headers (Authorization redacted to a 5-char preview), and the raw response body. This is the server rejecting the exchange: 401 for a bad/expired GitHub token, 403 for no Copilot subscription, 404/429 for endpoint or rate issues. The response text embedded in the message tells you which.
Source
Thrown at aider/models.py:971
github_token = os.environ["GITHUB_COPILOT_TOKEN"]
if not github_token.strip():
raise KeyError("GITHUB_COPILOT_TOKEN environment variable is empty")
headers = {
"Authorization": f"Bearer {os.environ['GITHUB_COPILOT_TOKEN']}",
"Editor-Version": extra_headers["Editor-Version"],
"Copilot-Integration-Id": extra_headers["Copilot-Integration-Id"],
"Content-Type": "application/json",
}
url = "https://api.github.com/copilot_internal/v2/token"
res = requests.get(url, headers=headers)
if res.status_code != 200:
safe_headers = {k: v for k, v in headers.items() if k != "Authorization"}
token_preview = github_token[:5] + "..." if len(github_token) >= 5 else github_token
safe_headers["Authorization"] = f"Bearer {token_preview}"
raise GitHubCopilotTokenError(
f"GitHub Copilot API request failed (Status: {res.status_code})\n"
f"URL: {url}\n"
f"Headers: {json.dumps(safe_headers, indent=2)}\n"
f"JSON: {res.text}"
)
response_data = res.json()
token = response_data.get("token")
if not token:
raise GitHubCopilotTokenError("Response missing 'token' field")
os.environ[openai_api_key] = token
def send_completion(self, messages, functions, stream, temperature=None):
if os.environ.get("AIDER_SANITY_CHECK_TURNS"):
sanity_check_messages(messages)
if self.is_deepseek_r1():View on GitHub (pinned to 5dc9490bb3)
Solutions
- Read the embedded JSON body in the error message: 401 'Bad credentials' means refresh your GITHUB_COPILOT_TOKEN; 403 suggests the account lacks a Copilot subscription.
- Re-run the auth flow that produced GITHUB_COPILOT_TOKEN and re-export a fresh token, then retry.
- Confirm the account has an active GitHub Copilot entitlement; without it the internal endpoint is forbidden.
- For 429s, back off and retry later — the internal token endpoint is rate-limited.
Example fix
# before: stale token exported weeks ago
export GITHUB_COPILOT_TOKEN="gho_old..." # later: GitHubCopilotTokenError: Status 401
# after: refresh and verify
export GITHUB_COPILOT_TOKEN="$(gh auth token)" # or re-run device-code flow
python -c "import os,requests; r=requests.get('https://api.github.com/copilot_internal/v2/token', headers={'Authorization': f'Bearer {os.environ["GITHUB_COPILOT_TOKEN"]}', 'Editor-Version': 'aider/1', 'Copilot-Integration-Id': 'vscode-chat'}); print(r.status_code)" # expect 200 Defensive patterns
Strategy: retry
Validate before calling
import os, requests
def copilot_token_exchange_ok():
"""Probe the token exchange endpoint; True = credentials healthy."""
tok = os.environ.get("GITHUB_COPILOT_TOKEN", "")
if not tok.strip():
return False
h = {
"Authorization": f"Bearer {tok}",
"Editor-Version": "aider/probe",
"Copilot-Integration-Id": "vscode-chat",
}
try:
return requests.get(
"https://api.github.com/copilot_internal/v2/token", headers=h, timeout=10
).status_code == 200
except requests.RequestException:
return False Try / catch
import time
for attempt in range(3):
try:
out = model.send_completion(messages, None, False)
break
except Exception as e:
msg = str(e)
if "401" in msg or "403" in msg:
raise SystemExit("Copilot token rejected/expired — re-authenticate.")
if "429" in msg and attempt < 2:
time.sleep(2 ** attempt) # rate limited: back off and retry
continue
raise Prevention
- Treat 401 as a hard stop: refresh the GitHub token instead of retrying.
- Add exponential backoff around the token exchange for 429/5xx responses.
- Probe the endpoint once at startup (cheap GET) so bad credentials fail fast with a clear message.
When it happens
Trigger: Any non-200 from the token endpoint during a Copilot model call: expired or revoked gho_ GitHub token (401), account without an active Copilot subscription (403), rate limiting on the internal endpoint (429), or GitHub API incidents. Only fires when the cached Copilot token is expired, so symptoms can start mid-session.
Common situations: Long-lived sessions where the GitHub OAuth token quietly expired or was revoked by a password change/re-auth elsewhere; using Copilot models on an account whose subscription lapsed; heavy automation hitting the internal token endpoint and being throttled.
Related errors
- GITHUB_COPILOT_TOKEN environment variable not found
- GITHUB_COPILOT_TOKEN environment variable is empty
- Response missing 'token' field
- Unknown edit format {edit_format}. Valid formats are: {', '.
- No data found in LLM response!
AI-assisted analysis of Aider-AI/aider@5dc9490bb3 (2026-08-15).
Data as JSON: /api/errors/b97f24f455d294d6.
Report an issue: GitHub.