{"record":{"id":"861b289b44242c22","repo":"odysseus-dev/odysseus","slug":"chatgpt-token-response-was-missing-access-token-or","errorCode":null,"errorMessage":"ChatGPT token response was missing access_token or refresh_token","messagePattern":"ChatGPT token response was missing access_token or refresh_token","errorType":"http","errorClass":"ValueError","httpStatus":502,"severity":"error","filePath":"routes/chatgpt_subscription_routes.py","lineNumber":29,"sourceCode":"from routes.device_flow import (\n    DeviceFlowPoll,\n    DeviceFlowStart,\n    PendingDeviceFlowStore,\n    create_device_flow_router,\n)\nfrom src.auth_helpers import get_current_user\nfrom src import chatgpt_subscription\n\nlogger = logging.getLogger(__name__)\n\n_DEVICE_FLOW_STORE = PendingDeviceFlowStore()\n\n\ndef _provision_endpoint(tokens: Dict, owner: Optional[str]) -> Dict:\n    access_token = tokens.get(\"access_token\")\n    refresh_token = tokens.get(\"refresh_token\")\n    if not access_token or not refresh_token:\n        raise ValueError(\"ChatGPT token response was missing access_token or refresh_token\")\n\n    base = chatgpt_subscription.DEFAULT_CHATGPT_SUBSCRIPTION_BASE_URL\n    models = chatgpt_subscription.fetch_available_models(access_token)\n    if not models:\n        raise ValueError(\"ChatGPT Subscription connected, but no usable Codex models were discovered for this account.\")\n    db = SessionLocal()\n    try:\n        auth = (\n            db.query(ProviderAuthSession)\n            .filter(\n                ProviderAuthSession.provider == chatgpt_subscription.CHATGPT_SUBSCRIPTION_PROVIDER,\n                ProviderAuthSession.owner == owner,\n            )\n            .first()\n        )\n        if auth is None:\n            auth = ProviderAuthSession(\n                id=str(uuid.uuid4())[:8],","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/chatgpt_subscription_routes.py#L11-L47","documentation":"A server-side ValueError raised in _provision_endpoint when the ChatGPT OAuth device-flow token exchange returns a payload without access_token or refresh_token. It aborts endpoint provisioning before any DB record is written; the caller of the route typically surfaces it as a 500/4xx depending on outer handling. It indicates the OAuth exchange with the ChatGPT/Codex issuer succeeded HTTP-wise but returned an incomplete or unexpected token payload.","triggerScenarios":"Completing ChatGPT Subscription device-code login where the token endpoint response is missing fields (issuer API change), the response was an error envelope parsed as a token dict, a proxy/interceptor mangled the JSON, or the code exchanged a device code that had already been consumed/expired.","commonSituations":"OpenAI changes the ChatGPT OAuth response shape; clock skew or expired device_code producing an error body that still parses as a dict; retrying a device flow after the user refreshed the page and the code was already redeemed; corporate MITM proxy stripping response fields.","solutions":["Restart the device flow from the beginning (request a new device code) instead of retrying the exchange — device codes are single-use.","Check server logs for the raw token response shape from chatgpt_subscription's exchange call to confirm which field is missing.","Update to the latest version of the app/chatgpt_subscription module in case the issuer response format changed.","If behind a proxy, bypass it for the ChatGPT OAuth issuer domain and retry."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"def has_token_fields(tokens: dict) -> bool:\n    return bool(tokens.get('access_token')) and bool(tokens.get('refresh_token'))","tryCatchPattern":"# server-side caller of _provision_endpoint\ntry:\n    _provision_endpoint(tokens, owner)\nexcept ValueError as e:\n    if 'missing access_token or refresh_token' in str(e):\n        _DEVICE_FLOW_STORE.clear()  # stale/used device code; restart flow\n        raise HTTPException(502, 'Token exchange incomplete, restart the connection flow')\n    raise","preventionTips":["Never re-submit a used device code — always begin a fresh device flow after any exchange failure.","Log (safe, non-secret) key names of the token response at debug level to detect issuer schema drift early.","Pin a known-good version of the OAuth client and test after issuer-side API changes."],"tags":["oauth","device-flow","chatgpt","token-exchange","server-side"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}