headroomlabs-ai/headroom · error · RuntimeError
Copilot token exchange returned an empty token.
Error message
Copilot token exchange returned an empty token.
What it means
Raised in CopilotTokenProvider._exchange_token after a successful HTTP exchange with GitHub's Copilot token endpoint: the response payload parsed, but payload['token'] is missing, empty, or whitespace-only after strip. This means the OAuth credential was accepted enough to get a 200 response yet no usable API token came back — most often the Copilot entitlement is absent from the GitHub account.
Source
Thrown at headroom/copilot_auth.py:1186
if not _should_exchange_oauth_token():
direct_token = CopilotAPIToken(
token=oauth_token,
expires_at=time.time() + 3600,
api_url=_configured_api_url(),
)
self._cached = direct_token
return direct_token
exchanged = await self._exchange_token(oauth_token)
self._cached = exchanged
return exchanged
async def _exchange_token(self, oauth_token: str) -> CopilotAPIToken:
headers = _copilot_token_exchange_headers(oauth_token)
payload = await asyncio.to_thread(self._exchange_token_sync, headers)
token = str(payload.get("token") or "").strip()
if not token:
raise RuntimeError("Copilot token exchange returned an empty token.")
expires_at = _parse_expiry(payload.get("expires_at")) or (time.time() + 1800)
api_url = await asyncio.to_thread(
_api_url_from_exchange_payload,
payload,
oauth_token=oauth_token,
)
refresh_in = payload.get("refresh_in")
sku = payload.get("sku")
return CopilotAPIToken(
token=token,
expires_at=expires_at,
api_url=api_url,
refresh_in=int(refresh_in) if isinstance(refresh_in, int | float) else None,
sku=str(sku) if isinstance(sku, str) and sku.strip() else None,
)
@staticmethodView on GitHub (pinned to 322425c43b)
Solutions
- Confirm the GitHub account actually has a Copilot entitlement (individual subscription or an org seat) at github.com/settings/copilot
- If a seat was just assigned, wait briefly and retry — provisioning can lag
- Re-login to refresh the OAuth token in case it belongs to a different account than the one with Copilot
- Inspect the exchange response (log payload keys, never the token) to distinguish 'no entitlement' from shape drift
Defensive patterns
Strategy: try-catch
Try / catch
try:
api_token = await provider.get_token()
except RuntimeError as e:
if "empty token" in str(e):
raise SystemExit(
"Copilot exchange returned no token — verify the account has a Copilot "
"subscription/seat at github.com/settings/copilot"
) from e
raise Prevention
- Verify the Copilot entitlement on the account before scripting token exchange
- After assigning an org seat, allow provisioning time before first use
- Log exchange payload keys (never values) to distinguish entitlement gaps from endpoint changes
When it happens
Trigger: Exchange GET to _token_exchange_url() returns 200 with JSON lacking a 'token' field — e.g. the account has no Copilot subscription (free/paid) attached, the SKU is not provisioned, or an unexpected payload shape from a changed endpoint.
Common situations: Personal access token or OAuth token from an account without Copilot access; organization Copilot seat not yet assigned; attempting immediately after seat purchase before provisioning; endpoint contract drift returning an error object with HTTP 200.
Related errors
- Copilot token exchange failed with HTTP {exc.code}: {body}
- Copilot OAuth token must not be empty.
- No GitHub Copilot OAuth token is available.
- bedrock_sigv4_failed
- GitHub device authorization returned an invalid response.
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/9f4623e300445ad0.
Report an issue: GitHub.