From da43de72b832f904cabf203397442f00dd4ead7c Mon Sep 17 00:00:00 2001 From: b3nw Date: Tue, 30 Jun 2026 00:44:44 +0000 Subject: [PATCH 1/2] fix(model): repair bare custom-provider suffix on resolver slow path (#5225) Mirror the #5128 fast-path suffix repair when profile context is present but requested_provider is omitted, which async delegation completion can trigger via process_wakeup. Co-authored-by: b3nw --- api/routes.py | 18 ++++++++ ...est_issue5127_process_wakeup_bare_model.py | 41 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/api/routes.py b/api/routes.py index 77c48254..08e7a358 100644 --- a/api/routes.py +++ b/api/routes.py @@ -5578,6 +5578,24 @@ def _resolve_compatible_session_model_state( _target = _profile_default or default_model return _target, profile_provider, True + # Async server-side continuations (for example delegate_task completion + # re-entry) can arrive here with profile context but without a usable + # requested_provider, bypassing the fast-path custom-provider repair + # above. If the profile's configured custom-provider default is a + # slash-qualified model whose suffix matches the bare session model, + # repair back to the profile default before the provider call (#5225). + if ( + "/" not in model + and _profile_default + and "/" in _profile_default + and _profile_default.rsplit("/", 1)[-1] == model + and ( + _profile_provider_normalized == "custom" + or str(profile_provider).startswith("custom:") + ) + ): + return _profile_default, profile_provider, True + return model, profile_provider, False if not model: diff --git a/tests/test_issue5127_process_wakeup_bare_model.py b/tests/test_issue5127_process_wakeup_bare_model.py index 40ed5f3a..849c5eae 100644 --- a/tests/test_issue5127_process_wakeup_bare_model.py +++ b/tests/test_issue5127_process_wakeup_bare_model.py @@ -82,6 +82,47 @@ class TestIssue5127CustomProviderBareSuffixRepair: assert mock_catalog.call_count == 0 assert result == ("x-ai/grok-composer-2.5-fast", "custom:my-proxy", False) + def test_slow_path_repairs_bare_suffix_to_profile_default_for_custom_provider(self): + """Regression #5225: async continuations may arrive without usable model_provider.""" + from api.routes import _resolve_compatible_session_model_state + + with patch("api.routes.get_available_models") as mock_catalog: + mock_catalog.return_value = { + "active_provider": "openrouter", + "default_model": "openai/gpt-5.5", + "groups": [], + } + result = _resolve_compatible_session_model_state( + "grok-composer-2.5-fast", + None, + profile_provider="custom:my-proxy", + profile_default_model="x-ai/grok-composer-2.5-fast", + prefer_cached_catalog=True, + ) + + assert mock_catalog.call_count == 1 + assert result == ("x-ai/grok-composer-2.5-fast", "custom:my-proxy", True) + + def test_slow_path_does_not_rewrite_unrelated_bare_model_for_custom_provider(self): + from api.routes import _resolve_compatible_session_model_state + + with patch("api.routes.get_available_models") as mock_catalog: + mock_catalog.return_value = { + "active_provider": "openrouter", + "default_model": "openai/gpt-5.5", + "groups": [], + } + result = _resolve_compatible_session_model_state( + "other-model", + None, + profile_provider="custom:my-proxy", + profile_default_model="x-ai/grok-composer-2.5-fast", + prefer_cached_catalog=True, + ) + + assert mock_catalog.call_count == 1 + assert result == ("other-model", "custom:my-proxy", False) + def test_openai_codex_stale_openai_slash_still_uses_slow_path(self): """Regression: openai/... under openai-codex must not take bare fast return.""" from api.routes import _resolve_compatible_session_model_state From 15492b9f29ef628c3ab7825467221f24f91c1fc4 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Tue, 30 Jun 2026 01:13:48 +0000 Subject: [PATCH 2/2] docs(changelog): repair bare custom-provider suffix on resolver slow path (#5246) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5f2b1f2..a93db487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ ### Fixed +- **Async continuations (delegate_task / process wakeup) no longer fall back to a bare, mis-routed model.** When a server-side continuation re-entered model resolution without a requested provider, the resolver's slow path skipped the custom-provider repair, so a bare session model wasn't re-qualified to the profile's `custom:…/model` default and could be sent to the wrong endpoint. The slow path now repairs a bare model back to the profile's slash-qualified custom-provider default when the suffix matches (mirroring the fast path). Thanks @claw-io. (#5246, fixes #5225) + - **Settled transcripts with duplicate assistant turns no longer mis-attribute tool-output rendering.** When a reloaded conversation contained byte-identical assistant messages, the legacy tool-metadata fallback used `indexOf` to test anchor ownership, which always resolved to the *first* matching message — so an anchor-owned turn could be wrongly treated as a legacy fallback (or vice versa). Ownership is now gated by each message's actual raw index, and the shared check is consolidated into one helper used at both call sites. Thanks @franksong2702. (#5242) - **The session sidebar stays fast with a very large archive, and archived search still finds everything.** Archived sidebar rows are now paged (capped per request) instead of all loaded at once, so an instance with thousands of archived sessions no longer pays to render them all. When you turn on "Show archived" and start a search, the sidebar transparently refetches without the archive cap so a title/id match beyond the first archived page is still reachable, then restores normal paging when the search clears. Default (non-search) paging keeps the existing 2000-row safety cap, and a render-generation guard prevents a slower capped/uncapped response from overwriting newer sidebar state. Thanks @santastabber. (#5200)