odysseus-dev/odysseus · error · HTTPException
GitHub did not return a device code
Error message
GitHub did not return a device code
What it means
Raised (HTTP 502) by the Copilot device-flow start helper when the device-code endpoint returned HTTP 2xx but the JSON body has no 'device_code' field. It means GitHub (or GHES) accepted the request but responded with an unexpected payload — the app cannot proceed without a device_code to poll against.
Source
Thrown at routes/copilot_routes.py:117
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={
"user_code": data.get("user_code"),
"verification_uri": data.get("verification_uri"),
"verification_uri_complete": data.get("verification_uri_complete"),
},
interval=int(data.get("interval") or 5),
expires_in=int(data.get("expires_in") or 900),View on GitHub (pinned to f9235ebbf1)
Solutions
- Reproduce the raw request to see the actual body: curl -s https://github.com/login/device/code -d 'client_id=<id>' -d 'scope=read:user'.
- If a proxy intercepts HTTPS, bypass it for the GitHub/enterprise host.
- On GHES, confirm the version supports the device flow grant and the OAuth app is allowed to use it.
- Retry once — a transient 200-with-error from a load balancer is possible.
Defensive patterns
Strategy: retry
Try / catch
try { start = await post('/copilot/device/start', form); } catch (e) { if (e.status === 502 && /did not return a device code/.test(e.message)) { await sleep(2000); return retryOnce(); } throw e; } Prevention
- On GitHub Enterprise Server, confirm the instance version supports the device flow grant before enabling Copilot login.
- Bypass HTTPS-intercepting proxies for the GitHub host so OAuth JSON responses arrive intact.
When it happens
Trigger: POST to the device-flow start endpoint where the provider responds 200 with an error-shaped body (e.g. {'error': 'unauthorized_client'}), an HTML error page that happened to parse, or an API contract change / GHES version that returns a different schema. Rare on github.com, more plausible on older GitHub Enterprise Server versions.
Common situations: GHES instance on an older/newer version whose device endpoint returns an error body with 200; an intermediate proxy (captive portal, corporate MITM) returning a 200 HTML page instead of the OAuth response.
Related errors
- GitHub device-code request failed (HTTP {status})
- GitHub device-code request failed: {e}
- Request failed (HTTP ${response.status})
- Unknown device-flow provider: ${provider}
- ${cfg.label} sign-in did not return a poll id
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/243eec197cbb835f.
Report an issue: GitHub.