Codex robustness: logged-out detection + model listing#378
Merged
Conversation
Codex doesn't emit a Claude-style "not logged in" message; when its token is revoked the app-server sends an unauthorized/token-revoked error event, which fell through as an unhandled runtime_specific event — so the login modal never opened. Map that error to a neutral auth_required yokeType in the Codex adapter, and handle auth_required in the message processor by emitting the same login flow (WS message + notification) used for Claude. Extract the auth-required emission into a shared, per-session-deduped helper so both vendors go through one path. Codex-specific error detection stays in the adapter; the relay only sees the neutral yokeType.
A new Codex session showed a Claude model in the picker: get_vendor_models awaited codex init(), and when the app-server `initialize` handshake timed out, model listing threw and the picker stayed on the previous vendor's model. Codex models are a fixed list, so decouple them from init: hoist the list to a CODEX_MODELS constant that supportedModels() returns directly, and in get_vendor_models don't let a failed init() skip supportedModels().
The logged-out modal only fired for the clean unauthorized error event.
When Codex retries the responses websocket it instead surfaces the 401 as
turn/failed and item errors ("401 Unauthorized", "Missing bearer", "sign
in again"), which slipped through as generic errors — no login modal.
Add a shared isCodexAuthError() and apply it at every error emit point
(turn/failed, item error, top-level error, run-loop catch), mapping auth
failures to the neutral auth_required yokeType. The processor already
dedupes auth_required per session, so repeated 401 retries collapse to a
single login modal.
Opening a Codex session while the last model was a Claude one left the chip showing the Claude model: the model_info handler kept the existing selection whenever vendorSelectionLocked was set, even if that model didn't belong to the new vendor. Now the existing selection is kept only when it's actually in the vendor's model list; otherwise it snaps to the server-provided default (mirrors the server's modelToSend logic).
The definitive Codex "not logged in" signal is a 401 on its responses endpoint, which only shows up on the app-server's stderr — the JSON-RPC error events carry a generic message, so the login modal never fired. Watch stderr for the 401/auth patterns and synthesize an unauthorized error event into the adapter's event handler, which maps to the neutral auth_required yokeType. Deduped (15s) so repeated 401 retries collapse to a single login modal.
Contributor
|
This issue has been resolved in version 2.45.0-beta.4 (main). To update, run: -- Clay Deploy Bot Build anything, with anyone, in one place. |
Contributor
|
This issue has been resolved in version 2.45.0 (stable). To update, run: -- Clay Deploy Bot Build anything, with anyone, in one place. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two Codex adapter fixes.
1. Detect logged-out Codex → trigger the login modal
Claude reports "not logged in" as a login-prompt message; Codex instead emits an unauthorized / token-revoked error event (
codexErrorInfo: "unauthorized", HTTP 401 token_revoked). That fell through the adapter as an unhandledruntime_specificevent, soauth_requirednever fired and the login modal never opened.errorevent to a neutralauth_requiredyokeType (vendor: codex); other top-level errors surface as normalerror.yokeType === "auth_required"via a shared, per-session-dedupedemitAuthRequired(session)helper (also used by the Claude login-prompt path). Codex-specific detection stays in the adapter.Result:
codex logout+ send a message now pops the login modal runningcodex login --device-auth.2. New Codex session showed a Claude model in the picker
get_vendor_modelsawaited Codexinit(); when the app-serverinitializehandshake timed out, model listing threw and the picker stayed on the previous vendor's model (e.g.claude-opus-4-8).CODEX_MODELSconstant thatsupportedModels()returns directly, independent of init.get_vendor_modelsno longer lets a failedinit()skipsupportedModels().Result: a new Codex session lists the gpt-5.x models immediately, even if the app-server init is slow/unavailable.
Validation
node --check+ require smoke on all touched files; relay purity preserved (no codex error strings in the processor). Runtime pending: confirm on a logged-out Codex session (modal pops) and that a fresh Codex session shows a gpt model.