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

  1. 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.
  2. Re-run the auth flow that produced GITHUB_COPILOT_TOKEN and re-export a fresh token, then retry.
  3. Confirm the account has an active GitHub Copilot entitlement; without it the internal endpoint is forbidden.
  4. 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

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


AI-assisted analysis of Aider-AI/aider@5dc9490bb3 (2026-08-15). Data as JSON: /api/errors/b97f24f455d294d6. Report an issue: GitHub.