odysseus-dev/odysseus · error · ValueError

ChatGPT token response was missing access_token or refresh_t

Error message

ChatGPT token response was missing access_token or refresh_token

What it means

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.

Source

Thrown at routes/chatgpt_subscription_routes.py:29

from routes.device_flow import (
    DeviceFlowPoll,
    DeviceFlowStart,
    PendingDeviceFlowStore,
    create_device_flow_router,
)
from src.auth_helpers import get_current_user
from src import chatgpt_subscription

logger = logging.getLogger(__name__)

_DEVICE_FLOW_STORE = PendingDeviceFlowStore()


def _provision_endpoint(tokens: Dict, owner: Optional[str]) -> Dict:
    access_token = tokens.get("access_token")
    refresh_token = tokens.get("refresh_token")
    if not access_token or not refresh_token:
        raise ValueError("ChatGPT token response was missing access_token or refresh_token")

    base = chatgpt_subscription.DEFAULT_CHATGPT_SUBSCRIPTION_BASE_URL
    models = chatgpt_subscription.fetch_available_models(access_token)
    if not models:
        raise ValueError("ChatGPT Subscription connected, but no usable Codex models were discovered for this account.")
    db = SessionLocal()
    try:
        auth = (
            db.query(ProviderAuthSession)
            .filter(
                ProviderAuthSession.provider == chatgpt_subscription.CHATGPT_SUBSCRIPTION_PROVIDER,
                ProviderAuthSession.owner == owner,
            )
            .first()
        )
        if auth is None:
            auth = ProviderAuthSession(
                id=str(uuid.uuid4())[:8],

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Restart the device flow from the beginning (request a new device code) instead of retrying the exchange — device codes are single-use.
  2. Check server logs for the raw token response shape from chatgpt_subscription's exchange call to confirm which field is missing.
  3. Update to the latest version of the app/chatgpt_subscription module in case the issuer response format changed.
  4. If behind a proxy, bypass it for the ChatGPT OAuth issuer domain and retry.
Defensive patterns

Strategy: retry

Type guard

def has_token_fields(tokens: dict) -> bool:
    return bool(tokens.get('access_token')) and bool(tokens.get('refresh_token'))

Try / catch

# server-side caller of _provision_endpoint
try:
    _provision_endpoint(tokens, owner)
except ValueError as e:
    if 'missing access_token or refresh_token' in str(e):
        _DEVICE_FLOW_STORE.clear()  # stale/used device code; restart flow
        raise HTTPException(502, 'Token exchange incomplete, restart the connection flow')
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/861b289b44242c22. Report an issue: GitHub.