{"record":{"id":"555c79d5a0261bd9","repo":"langchain-ai/deepagents","slug":"device-code-response-from-device-code-url-is-mis","errorCode":null,"errorMessage":"Device code response from {device_code_url} is missing required fields: {exc}","messagePattern":"Device code response from (.+?) is missing required fields: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/mcp_auth.py","lineNumber":1918,"sourceCode":"            data=init_data,\n            headers={\"Accept\": \"application/json\"},\n        )\n        try:\n            response.raise_for_status()\n        except httpx.HTTPStatusError as exc:\n            msg = (\n                f\"Device code request failed: HTTP {response.status_code} \"\n                f\"from {device_code_url}.\"\n            )\n            raise RuntimeError(msg) from exc\n        try:\n            device = _DeviceCodeResponse.model_validate(response.json())\n        except (ValueError, ValidationError) as exc:\n            msg = (\n                f\"Device code response from {device_code_url} is missing \"\n                f\"required fields: {exc}\"\n            )\n            raise RuntimeError(msg) from exc\n\n        await interaction.show_device_code(\n            verification_uri=device.verification_uri,\n            user_code=device.user_code,\n            expires_in=device.expires_in,\n        )\n\n        interval = max(device.interval, 1)\n        loop = asyncio.get_running_loop()\n        deadline = loop.time() + device.expires_in\n        while loop.time() < deadline:\n            await asyncio.sleep(interval)\n            token_response = await client.post(\n                token_url,\n                data={\n                    \"client_id\": client_id,\n                    \"device_code\": device.device_code,\n                    \"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",","sourceCodeStart":1900,"sourceCodeEnd":1936,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/mcp_auth.py#L1900-L1936","documentation":"After a successful device-code HTTP request, the JSON body is validated against the _DeviceCodeResponse schema (verification_uri, user_code, expires_in, etc.); missing/invalid fields raise this RuntimeError with the pydantic ValidationError details. The provider responded 200 but with a body that does not match the RFC 8628 device authorization response.","triggerScenarios":"_run_device_flow calls _DeviceCodeResponse.model_validate(response.json()) and validation fails - missing required fields, wrong types, or non-JSON body from the device endpoint.","commonSituations":"Provider returning an HTML error page with 200 behind a captive portal/proxy, a non-standard provider omitting fields like verification_uri_complete, an API version change, or an intercepted response from a corporate proxy.","solutions":["Inspect the ValidationError details in the message to see which required fields are missing and compare against the provider's device-flow docs.","Verify you are hitting the real provider endpoint (not a proxy/captive-portal page) - curl the URL and check the raw JSON.","If the provider is genuinely non-standard, use its authorization_code flow instead of the device flow, or update the server's OAuth metadata."],"exampleFix":"// before (provider response)\n{\"device_code\": \"...\"}  # missing user_code/verification_uri\n// after\ncheck endpoint: curl -s https://github.com/login/device/code -d client_id=...  # expect full RFC 8628 payload incl. user_code, verification_uri, expires_in","handlingStrategy":"validation","validationCode":"import httpx, json\nresp = httpx.post(device_code_url, data={\"client_id\": client_id})\nbody = resp.json()  # raises if HTML/captive-portal body\nrequired = {\"device_code\", \"user_code\", \"verification_uri\", \"expires_in\"}\nmissing = required - body.keys()\nassert not missing, f\"provider response missing fields: {missing}\"","typeGuard":"def is_valid_device_response(body: object) -> TypeGuard[dict]:\n    required = {\"device_code\", \"user_code\", \"verification_uri\", \"expires_in\"}\n    return isinstance(body, dict) and required.issubset(body.keys())","tryCatchPattern":"try:\n    token = await _run_device_flow(...)\nexcept RuntimeError as e:\n    if str(e).startswith(\"Device code response\"):\n        print(f\"Provider returned a non-standard device response: {e}. Check proxy/portal and provider docs.\")\n    else:\n        raise","preventionTips":["Check the raw device-endpoint JSON with curl to rule out captive portals or proxy HTML pages.","Confirm the provider implements RFC 8628 fully (user_code, verification_uri, expires_in).","Pin/verify the provider API version if it recently changed its device-flow response shape."],"tags":["mcp","oauth","device-flow","schema-validation","http"],"backgroundTag":"schema-validation-failed","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}