{"record":{"id":"e7c806b2858fe59a","repo":"odysseus-dev/odysseus","slug":"google-oauth-token-unavailable-reconnect-the-acc-e7c806","errorCode":null,"errorMessage":"Google OAuth token unavailable — reconnect the account in Settings → Integrations","messagePattern":"Google OAuth token unavailable — reconnect the account in Settings → Integrations","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"routes/email_helpers.py","lineNumber":1234,"sourceCode":"        )\n    # Connection mode:\n    #   STARTTLS on → plain + upgrade\n    #   STARTTLS off + port 993 → implicit SSL (IMAPS)\n    #   STARTTLS off + any other port → plain (local Dovecot, custom ports)\n    # The last branch is critical: previously this fell into IMAP4_SSL\n    # for any non-STARTTLS port, which would fail the TLS handshake on\n    # plain local servers (Dovecot on 31143, etc.).\n    conn = _open_imap_connection(\n        cfg[\"imap_host\"],\n        cfg[\"imap_port\"],\n        starttls=bool(cfg.get(\"imap_starttls\")),\n        timeout=timeout,\n    )\n    try:\n        if cfg.get(\"oauth_provider\") == \"google\":\n            token = _get_valid_google_token(cfg.get(\"account_id\"), cfg)\n            if not token:\n                raise RuntimeError(\"Google OAuth token unavailable — reconnect the account in Settings → Integrations\")\n            conn.authenticate(\"XOAUTH2\", lambda x: _xoauth2_bytes(cfg[\"imap_user\"], token))\n        else:\n            conn.login(cfg[\"imap_user\"], cfg[\"imap_password\"])\n    except Exception:\n        # A failed AUTHENTICATE (e.g. an Office 365 app password on an\n        # MFA-enabled tenant, #3174, or an expired/revoked OAuth token)\n        # otherwise orphans the already-connected socket; close it before\n        # propagating so a misconfigured account can't leak one descriptor\n        # per retry / background poller pass.\n        try:\n            conn.shutdown()\n        except Exception:\n            pass\n        raise\n    return conn\n\n\nfrom contextlib import contextmanager","sourceCodeStart":1216,"sourceCodeEnd":1252,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/email_helpers.py#L1216-L1252","documentation":"RuntimeError raised during IMAP AUTHENTICATE when oauth_provider=='google' but _get_valid_google_token yields no token — stored access token absent/expired and the refresh attempt failed. The surrounding except shuts the socket down before propagating so failed auths don't leak descriptors.","triggerScenarios":"Polling or reading an inbox of a Google OAuth account whose refresh token was revoked or expired, whose token row was deleted, or whose secret-storage decryption fails; any background inbox poll after the user disconnected Google in Settings.","commonSituations":"Google revoking refresh tokens on inactive OAuth apps in testing mode; secret storage key rotated between deployments; user removed app access from their Google account but the local account row still says google.","solutions":["Reconnect the Google account via Settings → Integrations to obtain a fresh refresh token.","If the account should use password auth instead, clear oauth_provider so conn.login() is used.","Disable background polling for accounts whose OAuth creds are known-bad until reconnected."],"exampleFix":"# before: poller crashes each pass\nwith _imap(account_id, owner=owner) as conn: inbox_poll(conn)\n# after: skip unrecoverable auth failures\ntry:\n    with _imap(account_id, owner=owner) as conn: inbox_poll(conn)\nexcept RuntimeError as e:\n    if 'OAuth token unavailable' in str(e):\n        mark_account_needs_reconnect(account_id)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    with _imap(account_id, owner=owner) as conn: poll(conn)\nexcept RuntimeError as e:\n    if 'OAuth token unavailable' in str(e):\n        disable_polling(account_id); notify_reconnect_needed(account_id)\n    else: raise","preventionTips":["Skip background polls for accounts flagged needs-reconnect instead of erroring every pass.","Reconnect Google immediately after revoking/changing anything in the Google account.","Monitor for this message to catch silent refresh-token death early."],"tags":["oauth2","gmail","imap","token-refresh","email"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}