odysseus-dev/odysseus · error · HTTPException

GitHub device-code request failed (HTTP {status})

Error message

GitHub device-code request failed (HTTP {status})

What it means

Raised (HTTP 502) by the Copilot device-flow login start helper when copilot.request_device_code(host) fails with an httpx.HTTPStatusError — i.e. GitHub's (or GitHub Enterprise Server's) device-code endpoint returned a non-2xx HTTP status. The 502 signals the failure is upstream at the OAuth provider, not in this app; the status code from the failed response (or 'unknown' if the exception carries no response) is embedded in the message. Host selection honors an enterprise_url form field normalized via copilot.normalize_domain.

Source

Thrown at routes/copilot_routes.py:111

    # Best-effort: refresh the model cache so the new endpoint shows up.
    try:
        from routes.model_routes import _invalidate_models_cache
        _invalidate_models_cache()
    except Exception:
        pass
    return result


def _start_device_flow(request: Request, form) -> DeviceFlowStart:
    host = copilot.GITHUB_HOST
    ent = str(form.get("enterprise_url") or "").strip()
    if ent:
        host = copilot.normalize_domain(ent)
    try:
        data = copilot.request_device_code(host)
    except httpx.HTTPStatusError as e:
        status = e.response.status_code if e.response is not None else "unknown"
        raise HTTPException(502, f"GitHub device-code request failed (HTTP {status})")
    except Exception as e:
        raise HTTPException(502, f"GitHub device-code request failed: {e}")

    device_code = data.get("device_code")
    if not device_code:
        raise HTTPException(502, "GitHub did not return a device code")

    # verification_uri_complete embeds the user code, so the browser tab we
    # open lands the user straight on GitHub's "Authorize" screen with the
    # code pre-filled — one click, no manual code entry.
    return DeviceFlowStart(
        pending={
            "device_code": device_code,
            "host": host,
            "enterprise_url": ent,
            "owner": get_current_user(request) or None,
        },
        response={

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. If enterprise_url was entered, verify it is the correct GitHub Enterprise Server domain and that device-flow OAuth is enabled there; otherwise clear it to use github.com.
  2. Wait and retry if the embedded status is 429 (rate limited) — repeated login restarts compound the limit.
  3. If status is 401/403/404 on plain github.com, check the app's Copilot OAuth client configuration (client_id) in the copilot module.
  4. Check https://www.githubstatus.com for 5xx-side outages when the status is 500+.
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: the device endpoint should respond before starting login
async function githubReachable(host = 'github.com') {
  const res = await fetch(`https://${host}/login/device/code`, { method: 'HEAD' }).catch(() => null);
  return res !== null;
}

Try / catch

try { start = await post('/copilot/device/start', form); } catch (e) { if (e.status === 502 && /HTTP (429|5\d\d)/.test(e.message)) { await backoff(); return retryStart(); } if (/HTTP 40[13]/.test(e.message)) { showHelp('Check enterprise_url / OAuth client config'); } throw e; }

Prevention

When it happens

Trigger: Starting Copilot device-code login while github.com/login/device/code (or the GHES equivalent at the enterprise domain) returns an error status: 401/403 for a bad or expired OAuth client_id, 404 when the enterprise URL is wrong or the instance does not enable device flow, 429 rate limiting, 5xx outages.

Common situations: Misconfigured enterprise_url (points at a GHES host where the device-flow app is not registered); GitHub rate-limits device-code requests during repeated login attempts; GitHub or GHES outage; the app's Copilot OAuth client credentials were revoked.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/8bce850a4a745758. Report an issue: GitHub.