{"record":{"id":"243eec197cbb835f","repo":"odysseus-dev/odysseus","slug":"github-did-not-return-a-device-code","errorCode":null,"errorMessage":"GitHub did not return a device code","messagePattern":"GitHub did not return a device code","errorType":"http","errorClass":"HTTPException","httpStatus":502,"severity":"error","filePath":"routes/copilot_routes.py","lineNumber":117,"sourceCode":"    return result\n\n\ndef _start_device_flow(request: Request, form) -> DeviceFlowStart:\n    host = copilot.GITHUB_HOST\n    ent = str(form.get(\"enterprise_url\") or \"\").strip()\n    if ent:\n        host = copilot.normalize_domain(ent)\n    try:\n        data = copilot.request_device_code(host)\n    except httpx.HTTPStatusError as e:\n        status = e.response.status_code if e.response is not None else \"unknown\"\n        raise HTTPException(502, f\"GitHub device-code request failed (HTTP {status})\")\n    except Exception as e:\n        raise HTTPException(502, f\"GitHub device-code request failed: {e}\")\n\n    device_code = data.get(\"device_code\")\n    if not device_code:\n        raise HTTPException(502, \"GitHub did not return a device code\")\n\n    # verification_uri_complete embeds the user code, so the browser tab we\n    # open lands the user straight on GitHub's \"Authorize\" screen with the\n    # code pre-filled — one click, no manual code entry.\n    return DeviceFlowStart(\n        pending={\n            \"device_code\": device_code,\n            \"host\": host,\n            \"enterprise_url\": ent,\n            \"owner\": get_current_user(request) or None,\n        },\n        response={\n            \"user_code\": data.get(\"user_code\"),\n            \"verification_uri\": data.get(\"verification_uri\"),\n            \"verification_uri_complete\": data.get(\"verification_uri_complete\"),\n        },\n        interval=int(data.get(\"interval\") or 5),\n        expires_in=int(data.get(\"expires_in\") or 900),","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/copilot_routes.py#L99-L135","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"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; }","preventionTips":["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."],"tags":["copilot","oauth","device-flow","github","response-schema"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}