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

  1. Run the headroom Copilot device login flow (which calls save_headroom_copilot_oauth_token) to populate the cache
  2. Check the auth file at headroom_copilot_auth_path() exists and its entry is not expired; re-login if it is
  3. 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

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


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/2d5c68c686d93fe5. Report an issue: GitHub.