From 293cb54f287b53f15f56427f31cddc6def5168fd Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sat, 12 Sep 2026 07:45:27 -0700 Subject: [PATCH] fix(agent): keep MoA verdicts fallback-free; nest the gated fallback under one `if` Two CI regressions from the previous commit, both mine: - `_moa_special_cases` was switched to `_ABORT_FALLBACK`, which flips `should_fallback` to True for the MoA adapter-shape and missing-preset verdicts. #55933 made those deliberately NOT fall back (a fallback would silently replace the MoA route with a single model); restore `retryable=False` only. The gate in `settle_unrecovered_error` now honours that for real: on main these verdicts never reached the fallback branch because the flag was never consulted. - `tests/agent/test_prompt_cache_ttl_propagation.py` pins every `_try_activate_fallback` reference to a direct `if agent._try_activate_fallback():` site (#84733 restart discipline); `fallback_allowed and agent._try_activate_fallback()` broke that shape. Nest the two calls under one `if should_fallback or local_validation:` block instead. --- agent/error_classifier.py | 4 ++-- agent/turn_api_error.py | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/agent/error_classifier.py b/agent/error_classifier.py index bd62005c4c..59b244b7b9 100644 --- a/agent/error_classifier.py +++ b/agent/error_classifier.py @@ -609,10 +609,10 @@ def _moa_special_cases(c: _Ctx) -> Optional[Verdict]: # Local MoA streaming adapter-shape bugs are not a provider outage; falling # back would silently replace the MoA route with a single model (#55933). if c.provider_slug == "moa" and any(s in str(c.error) for s in _MOA_ADAPTER_SHAPE_BUGS): - return _v(_R.format_error, **_ABORT_FALLBACK) + return _v(_R.format_error, retryable=False) # Persisted MoA preset name that was renamed/deleted — deterministic config error. from agent.errors import MoAPresetNotFoundError - return _v(_R.model_not_found, **_ABORT_FALLBACK) if isinstance(c.error, MoAPresetNotFoundError) else None + return _v(_R.model_not_found, retryable=False) if isinstance(c.error, MoAPresetNotFoundError) else None def _by_error_code(c: _Ctx) -> Optional[Verdict]: diff --git a/agent/turn_api_error.py b/agent/turn_api_error.py index df539e8dce..2500048196 100644 --- a/agent/turn_api_error.py +++ b/agent/turn_api_error.py @@ -308,18 +308,18 @@ def settle_unrecovered_error( # ``should_fallback=False`` marks a deterministic failure no other provider can fix (the # model's own malformed tool-call JSON, #12770): skip the cascade. Local validation errors # carry no classifier verdict and keep their historical fallback. - fallback_allowed = classified.should_fallback or is_local_validation_error - # Announce the fallback only when a chain exists, else "trying fallback..." lies - # before a silent abort. - if fallback_allowed and agent._has_pending_fallback(): - _label = _NONRETRYABLE_LABELS.get(classified.reason, f"Non-retryable error (HTTP {status_code})") - agent._buffer_status(f"⚠️ {_label} — trying fallback...") - if fallback_allowed and agent._try_activate_fallback(): - # Direct ``return _verdict("break")`` is load-bearing: the restart handler - # re-runs the pre-API preflight against the fallback's context window. - active_system_prompt = _arm_fallback_restart(agent, api_messages, active_system_prompt, _retry) - retry_count = compression_attempts = 0 - return _verdict("break") + if classified.should_fallback or is_local_validation_error: + # Announce the fallback only when a chain exists, else "trying fallback..." lies + # before a silent abort. + if agent._has_pending_fallback(): + _label = _NONRETRYABLE_LABELS.get(classified.reason, f"Non-retryable error (HTTP {status_code})") + agent._buffer_status(f"⚠️ {_label} — trying fallback...") + if agent._try_activate_fallback(): + # Direct ``return _verdict("break")`` is load-bearing: the restart handler + # re-runs the pre-API preflight against the fallback's context window. + active_system_prompt = _arm_fallback_restart(agent, api_messages, active_system_prompt, _retry) + retry_count = compression_attempts = 0 + return _verdict("break") return _verdict("return", nonretryable_client_error_result( agent, api_error, classified, status_code=status_code, api_kwargs=api_kwargs, api_messages=api_messages, messages=messages, conversation_history=conversation_history,