fix: model fallback — stale model on Nous login + connection error fallback (#6554)

Two bugs in the model fallback system:

1. Nous login leaves stale model in config (provider=nous, model=opus
   from previous OpenRouter setup). Fixed by deferring the config.yaml
   provider write until AFTER model selection completes, and passing the
   selected model atomically via _update_config_for_provider's
   default_model parameter. Previously, _update_config_for_provider was
   called before model selection — if selection failed (free tier, no
   models, exception), config stayed as nous+opus permanently.

2. Codex/stale providers in auxiliary fallback can't connect but block
   the auto-detection chain. Added _is_connection_error() detection
   (APIConnectionError, APITimeoutError, DNS failures, connection
   refused) alongside the existing _is_payment_error() check in
   call_llm(). When a provider endpoint is unreachable, the system now
   falls back to the next available provider instead of crashing.
This commit is contained in:
Teknium 2026-04-09 10:38:53 -07:00 committed by GitHub
parent 637ad443bf
commit 1ec1f6a68a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 52 additions and 6 deletions

View file

@ -3017,12 +3017,15 @@ def _login_nous(args, pconfig: ProviderConfig) -> None:
_save_provider_state(auth_store, "nous", auth_state)
saved_to = _save_auth_store(auth_store)
config_path = _update_config_for_provider("nous", inference_base_url)
print()
print("Login successful!")
print(f" Auth state: {saved_to}")
print(f" Config updated: {config_path} (model.provider=nous)")
# Resolve model BEFORE writing provider to config.yaml so we never
# leave the config in a half-updated state (provider=nous but model
# still set to the previous provider's model, e.g. opus from
# OpenRouter). The auth.json active_provider was already set above.
selected_model = None
try:
runtime_key = auth_state.get("agent_key") or auth_state.get("access_token")
if not isinstance(runtime_key, str) or not runtime_key:
@ -3056,9 +3059,6 @@ def _login_nous(args, pconfig: ProviderConfig) -> None:
unavailable_models=unavailable_models,
portal_url=_portal,
)
if selected_model:
_save_model_choice(selected_model)
print(f"Default model set to: {selected_model}")
elif unavailable_models:
_url = (_portal or DEFAULT_NOUS_PORTAL_URL).rstrip("/")
print("No free models currently available.")
@ -3070,6 +3070,15 @@ def _login_nous(args, pconfig: ProviderConfig) -> None:
print()
print(f"Login succeeded, but could not fetch available models. Reason: {message}")
# Write provider + model atomically so config is never mismatched.
config_path = _update_config_for_provider(
"nous", inference_base_url, default_model=selected_model,
)
if selected_model:
_save_model_choice(selected_model)
print(f"Default model set to: {selected_model}")
print(f" Config updated: {config_path} (model.provider=nous)")
except KeyboardInterrupt:
print("\nLogin cancelled.")
raise SystemExit(130)