{"record":{"id":"2e588e38eed49a2b","repo":"usestrix/strix","slug":"token-http-error","errorCode":"token_http_error","errorMessage":"token_http_error: HTTP {status_code}: {detail}","messagePattern":"token_http_error: HTTP (.+?): (.+?)","errorType":"error_code","errorClass":"CodexAuthError","httpStatus":null,"severity":"error","filePath":"strix/config/codex.py","lineNumber":234,"sourceCode":"\n\ndef _post_form(payload: dict[str, str]) -> dict[str, Any]:\n    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(","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/config/codex.py#L216-L252","documentation":"CodexAuthError with code `token_http_error` is raised when OpenAI's OAuth token endpoint returns an HTTP 4xx/5xx. The message embeds the status code and the first 300 bytes of the response body, so the exact failure (invalid_grant, invalid_client, server error) is visible in the exception text.","triggerScenarios":"Calling `exchange_code()` with an expired/reused authorization code, or `refresh_tokens()` with a revoked, rotated, or already-spent refresh token (single-use refresh tokens); the token endpoint responding 400/401/403/5xx.","commonSituations":"Two Strix processes racing to refresh with the same single-use refresh token (the loser gets this error; the code has a peer-recovery path in get_valid_token); logging out and back in elsewhere which invalidates old tokens; clock-skewed or expired auth records in ~/.strix/subscription-auth.json; OpenAI-side 5xx incidents.","solutions":["Re-authenticate from scratch: `strix auth logout && strix auth login` to mint a fresh token pair","If running multiple Strix processes concurrently, let the built-in cross-process refresh guard serialize them (it already exists); avoid deleting/copying ~/.strix/subscription-auth.json between machines","Inspect the embedded `detail` body — `invalid_grant` means the refresh token is spent/expired (re-login), `invalid_client`/5xx means wait or check OpenAI status","Retry once after a short delay for transient 5xx responses"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"from strix.config.codex import CodexAuthError\n\ntry:\n    token, account = get_valid_token()\nexcept CodexAuthError as e:\n    if e.code == \"token_http_error\" and \"invalid_grant\" in str(e):\n        subprocess.run([\"strix\", \"auth\", \"login\"], check=True)  # refresh token spent/expired\n    else:\n        raise","preventionTips":["Never copy ~/.strix/subscription-auth.json between machines or run parallel logins — refresh tokens are single-use","Log out before switching accounts: `strix auth logout` invalidates cleanly instead of leaving stale refresh tokens","Treat any 4xx containing invalid_grant as 're-login required'; only 5xx details are worth retrying"],"tags":["oauth","auth","http","codex","token"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}