headroomlabs-ai/headroom · error · RuntimeError

GitHub device authorization failed: {description}

Error message

GitHub device authorization failed: {description}

What it means

Generic failure branch of the device-flow token poll: GitHub returned a JSON dict with a non-empty 'error' that is neither authorization_pending, slow_down, nor expired_token. The error_description (falling back to the error code) is embedded in the RuntimeError. Typical underlying codes: incorrect_client_credentials, incorrect_device_code, access_denied (user clicked deny), unsupported_grant_type.

Source

Thrown at headroom/copilot_auth.py:598

            raise RuntimeError("GitHub device authorization returned an invalid response.")

        access_token = payload.get("access_token")
        if isinstance(access_token, str) and access_token.strip():
            return access_token.strip()

        error = str(payload.get("error") or "").strip()
        if error == "authorization_pending":
            time.sleep(poll_interval)
            continue
        if error == "slow_down":
            poll_interval += 5
            time.sleep(poll_interval)
            continue
        if error == "expired_token":
            raise RuntimeError("GitHub device authorization expired.")
        if error:
            description = str(payload.get("error_description") or error).strip()
            raise RuntimeError(f"GitHub device authorization failed: {description}")

        time.sleep(poll_interval)

    raise RuntimeError("GitHub device authorization expired.")


def _extract_oauth_token(entry: dict[str, Any]) -> str | None:
    if _entry_expired(entry):
        return None

    for key in _OAUTH_TOKEN_KEYS:
        value = entry.get(key)
        if isinstance(value, str) and value.strip():
            return value.strip()

    for value in entry.values():
        if isinstance(value, dict):
            nested = _extract_oauth_token(value)

View on GitHub (pinned to 322425c43b)

Solutions

  1. Read the embedded description: access_denied means the user denied consent — re-run and approve; incorrect_client_credentials means the client_id is wrong — check the configured GitHub App / client ID
  2. Start a brand-new device flow (fresh device_code) rather than reusing one
  3. On GitHub Enterprise, have an admin confirm the OAuth App permits the device flow grant
  4. Handle this as a non-retryable auth failure — do not loop, surface the message to the user
Defensive patterns

Strategy: try-catch

Try / catch

try:
    token = poll_copilot_device_authorization(device_code, ...)
except RuntimeError as e:
    msg = str(e)
    if "access_denied" in msg:
        raise SystemExit("User denied Copilot authorization — re-run and approve") from e
    if "incorrect_client_credentials" in msg:
        raise SystemExit("Client ID invalid — check the GitHub App configuration") from e
    raise  # other errors: do not retry blindly

Prevention

When it happens

Trigger: The user denies the consent page (access_denied); the client_id sent to the device_code endpoint is invalid or revoked (incorrect_client_credentials); the device_code is malformed/reused from a previous flow; device flow disabled for the GitHub App.

Common situations: Users pressing 'Cancel' on the GitHub authorize page; hardcoded/rotated client IDs in forks of the tool; GHE admins disabling OAuth device flow; retrying a device_code from an old run after it was consumed.

Related errors


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