headroomlabs-ai/headroom · error · RuntimeError
No GitHub Copilot OAuth token is available.
Error message
No GitHub Copilot OAuth token is available.
What it means
Raised inside CopilotTokenProvider when both credential sources are exhausted: no seeded refresh token was valid, and read_cached_oauth_token() returned None (no cached OAuth entry, or the cached entry was filtered out — e.g. by the _entry_expired check in _extract_oauth_token). The provider then cannot mint a Copilot API token at all, so it raises RuntimeError before attempting the token exchange.
Source
Thrown at headroom/copilot_auth.py:1166
if explicit_api_token and refresh_oauth_token:
if cached is None:
seeded_expires_at = _parse_expiry(os.environ.get(_API_TOKEN_EXPIRES_AT_ENV_VAR))
seeded = CopilotAPIToken(
token=explicit_api_token,
expires_at=seeded_expires_at if seeded_expires_at is not None else 0.0,
api_url=_configured_api_url(),
)
self._cached = seeded
if seeded.is_valid:
return seeded
exchanged = await self._exchange_token(refresh_oauth_token)
self._cached = exchanged
return exchanged
oauth_token = read_cached_oauth_token()
if not oauth_token:
raise RuntimeError("No GitHub Copilot OAuth token is available.")
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()View on GitHub (pinned to 322425c43b)
Solutions
- Run the headroom Copilot device login flow (which calls save_headroom_copilot_oauth_token) to populate the cache
- Check the auth file at headroom_copilot_auth_path() exists and its entry is not expired; re-login if it is
- In automation, pre-provision the auth file or fall back to an env-provided token instead of assuming a cache exists
Defensive patterns
Strategy: validation
Validate before calling
from headroom.copilot_auth import read_cached_oauth_token
if not read_cached_oauth_token():
raise SystemExit(
"No Copilot credentials cached — run `headroom` Copilot login (device flow) first"
) Try / catch
try:
api_token = await provider.get_token()
except RuntimeError as e:
if "No GitHub Copilot OAuth token" in str(e):
trigger_device_login() # then retry once
else:
raise Prevention
- Run the device login once per machine and keep the auth cache directory out of cleanup scripts
- Pre-check read_cached_oauth_token() before long jobs that will need Copilot tokens
- In CI, provision the auth file (or token env) as a setup step rather than assuming a cache
When it happens
Trigger: Calling the token provider (get_copilot_token_provider() / its token fetch) on a machine where `headroom` Copilot login never ran, where the cached auth file was deleted, or where the cached entry expired so _extract_oauth_token returned None.
Common situations: Fresh machine/CI container without prior device login; auth cache cleared by cleanup scripts or `unwrap`-adjacent tooling; entry's created_at/expiry making _entry_expired true; long gaps between runs exceeding the cached token's lifetime.
Related errors
- Copilot OAuth token must not be empty.
- GitHub device authorization returned an invalid response.
- GitHub device authorization expired.
- GitHub device authorization failed: {description}
- Copilot token exchange returned an empty token.
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/2d5c68c686d93fe5.
Report an issue: GitHub.