headroomlabs-ai/headroom · error · RuntimeError

GitHub device authorization returned an invalid response.

Error message

GitHub device authorization returned an invalid response.

What it means

Raised in the device-authorization request step of GitHub Copilot OAuth: the HTTP call to the device_code endpoint succeeded (status 200) but the JSON-decoded body is not a dict. GitHub's device flow always returns a JSON object (device_code, user_code, verification_uri, interval), so a non-dict payload means an unexpected response — a proxy, captive portal, or HTML error page that still parsed as JSON (e.g. a bare list or string). It is a RuntimeError, chained over json.loads output.

Source

Thrown at headroom/copilot_auth.py:542

    urls = _github_oauth_urls(domain)
    body = urlencode({"client_id": COPILOT_CHAT_OAUTH_CLIENT_ID, "scope": "read:user"}).encode(
        "utf-8"
    )
    request = urllib_request.Request(
        urls["device_code"],
        data=body,
        headers={
            "Accept": "application/json",
            "Content-Type": "application/x-www-form-urlencoded",
            "User-Agent": _DEFAULT_USER_AGENT,
        },
        method="POST",
    )
    with urllib_request.urlopen(request, timeout=timeout) as response:
        payload = json.loads(response.read().decode("utf-8", errors="replace"))
    if not isinstance(payload, dict):
        raise RuntimeError("GitHub device authorization returned an invalid response.")
    return payload


def poll_copilot_device_authorization(
    device_code: str,
    *,
    domain: str = DEFAULT_GITHUB_HOST,
    interval: int = 5,
    expires_in: int = 900,
    timeout: float = 10.0,
) -> str:
    """Poll GitHub until the device-code OAuth flow returns an access token."""

    urls = _github_oauth_urls(domain)
    deadline = time.time() + max(1, expires_in)
    poll_interval = max(1, interval)
    while time.time() < deadline:
        body = urlencode(

View on GitHub (pinned to 322425c43b)

Solutions

  1. Check DEFAULT_GITHUB_HOST / the domain argument points at a real GitHub or GHE instance (e.g. github.com)
  2. Bypass or configure the intercepting proxy (HTTPS_PROXY / NO_PROXY) so github.com is reached directly
  3. Reproduce with curl: `curl -s -X POST https://github.com/login/device/code -d 'client_id=...' -H 'Accept: application/json' | jq type` — anything other than 'object' confirms interception
  4. If on GHE, verify the device-flow endpoints exist on your server version (device flow needs GHE 3.x+)
Defensive patterns

Strategy: try-catch

Type guard

def is_device_auth_payload(payload: object) -> bool:
    return (
        isinstance(payload, dict)
        and isinstance(payload.get("device_code"), str)
        and isinstance(payload.get("user_code"), str)
    )

Try / catch

try:
    auth = start_copilot_device_authorization(...)
except RuntimeError as e:
    if "invalid response" in str(e):
        # proxy/interception or wrong GHE host — inspect network path, then restart flow
        raise SystemExit(f"Device auth response malformed: {e}") from e
    raise

Prevention

When it happens

Trigger: start_copilot_device_authorization() against https://github.com/login/device/code (or a GHE host) where a corporate proxy or misconfigured GITHUB_HOST returns a JSON scalar/array instead of the expected object.

Common situations: Corporate proxies that intercept HTTPS and return their own JSON; pointing domain at a GitHub Enterprise Server version whose endpoints differ; DNS hijack/captive portal responses; a typo in DEFAULT_GITHUB_HOST producing a 200 from a wildcard server.

Related errors


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