{"record":{"id":"cfe5c94286524bba","repo":"usestrix/strix","slug":"bad-response","errorCode":"bad_response","errorMessage":"bad_response: token endpoint returned non-object","messagePattern":"bad_response: token endpoint returned non-object","errorType":"error_code","errorClass":"CodexAuthError","httpStatus":null,"severity":"error","filePath":"strix/config/codex.py","lineNumber":237,"sourceCode":"    detail = \"\"\n    try:\n        with requests.post(\n            TOKEN_URL,\n            data=payload,\n            headers={\"Accept\": \"application/json\"},\n            timeout=_TOKEN_TIMEOUT,\n        ) as response:\n            status_code = response.status_code\n            body = response.content\n            if status_code >= 400:\n                detail = response.text[:300]\n    except requests.RequestException as exc:\n        raise CodexAuthError(\"unavailable\", str(exc)) from exc\n    if status_code >= 400:\n        raise CodexAuthError(\"token_http_error\", f\"HTTP {status_code}: {detail}\")\n    data = json.loads(body or b\"{}\")\n    if not isinstance(data, dict):\n        raise CodexAuthError(\"bad_response\", \"token endpoint returned non-object\")\n    return data\n\n\ndef _record_from_token_response(\n    data: dict[str, Any], refresh_fallback: str | None = None\n) -> dict[str, Any]:\n    access = data.get(\"access_token\")\n    # A refresh response may omit refresh_token when it isn't rotated; keep the old one.\n    refresh = data.get(\"refresh_token\") or refresh_fallback\n    expires_in = data.get(\"expires_in\")\n    if not isinstance(access, str) or not access:\n        raise CodexAuthError(\"bad_response\", \"token response missing access_token\")\n    if not isinstance(refresh, str) or not refresh:\n        raise CodexAuthError(\"bad_response\", \"token response missing refresh_token\")\n    account_id = _account_id_from_jwt(access) or _account_id_from_jwt(\n        data.get(\"id_token\") if isinstance(data.get(\"id_token\"), str) else \"\"\n    )\n    if not account_id:","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/config/codex.py#L219-L255","documentation":"CodexAuthError with code `bad_response` raised when the token endpoint returns HTTP 200 but the body is not a JSON object (e.g. a JSON array, string, number, or empty content coerced to `{}`). It means the endpoint answered successfully but the OAuth contract is violated.","triggerScenarios":"`exchange_code()` or `refresh_tokens()` receives a 200 response whose parsed JSON `isinstance(data, dict)` is False — a misbehaving gateway, HTML error page with 200 status, or API change at auth.openai.com.","commonSituations":"Captive portals or 'successful' proxy pages returning HTML with status 200; OpenAI changing the token endpoint shape; transparent content-inspection middleboxes mangling responses. Extremely rare in practice.","solutions":["Capture the raw body to identify what actually came back (add temporary logging of `body[:300]` before the parse)","If behind a proxy/VPN, bypass it for auth.openai.com and retry","If the endpoint contract changed, update to the latest Strix version which tracks the Codex CLI OAuth flow","Re-login: `strix auth login` after fixing the network path"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"from strix.config.codex import CodexAuthError\n\ntry:\n    data = exchange_code(code, verifier)\nexcept CodexAuthError as e:\n    if e.code == \"bad_response\" and \"non-object\" in str(e):\n        # endpoint/proxy misbehavior — one retry on a clean path, then surface\n        ...","preventionTips":["Bypass HTTP-intercepting proxies/VPN for auth.openai.com — transparent middleboxes cause 200-with-garbage responses","Pin to a maintained Strix version so OAuth-contract fixes are picked up","Retry once on transient bad_response before escalating; persistent recurrence means the network path is broken"],"tags":["oauth","auth","json","codex","token"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}