fix(auth): preserve custom:<subname> in credential pool lookup - #33700
Closed
Pluviobyte wants to merge 2 commits into
Closed
fix(auth): preserve custom:<subname> in credential pool lookup#33700Pluviobyte wants to merge 2 commits into
Pluviobyte wants to merge 2 commits into
Conversation
When a `model_aliases:` entry resolves through the direct-alias bare-custom
branch in `_resolve_named_custom_runtime` (i.e. `requested_provider` like
`custom:bobapi-deepseek` gets reduced to `requested_norm == "custom"` via
an alias that maps to "custom"), the credential-pool lookup was called
with `provider_name=None` and silently fell back to base_url-only matching.
When two or more `custom_providers` share a single `base_url` (a common
shape for Chinese aggregator APIs where each key is bound to a different
"分组"/group on the same domain), the url-only fallback always picked
whichever entry happened to appear first in iteration order, sending the
request with the wrong API key — surfacing upstream as a 503 / 404 that
looks like a model-availability problem but is actually a key-misroute.
Fix: split the sub-name off `requested_provider` and thread it through
`_try_resolve_from_custom_pool(..., provider_name=<subname>)` so name-based
pool selection wins over the url-only fallback. The kwarg form mirrors the
second pool-lookup site in the same file (line 695) that already passes
the name correctly via `custom_provider.get("name")`. Three regression
tests in `test_runtime_provider_resolution.py` cover the fix, the bare
`provider: custom` (no sub-name) baseline, and the end-to-end pool
selection when multiple providers share one base_url.
Salvages the auth-only subset of NousResearch#29893 (credit @vanhci); the original
PR was closed in review for mixing in unrelated WhatsApp / `uv pip` /
i18n changes — this PR ships only the `hermes_cli/runtime_provider.py`
fix plus its tests.
Fixes NousResearch#29872
Co-authored-by: Cursor <cursoragent@cursor.com>
The contributor-check workflow rejects noreply emails without a numeric ID prefix. Adding this mapping unblocks CI for this PR. Co-authored-by: Cursor <cursoragent@cursor.com>
Collaborator
Contributor
|
This is an automated hermes-sweeper review. Thank you for isolating the credential-pool change and preserving the prior contributor credit.
The PR's new tests force |
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.
What does this PR do?
When a
model_aliases:entry resolves through the direct-alias bare-custom branch inhermes_cli/runtime_provider.py::_resolve_named_custom_runtime(i.e.requested_providerlikecustom:bobapi-deepseekgets normalised torequested_norm == \"custom\"via an alias that maps to\"custom\"), the credential-pool lookup was called withprovider_name=Noneand silently fell back to base_url-only matching.When two or more
custom_providersshare a singlebase_url(a common shape for Chinese aggregator APIs where each key is bound to a different group on the same domain — the exact case from #29872), the url-only fallback always picked whichever entry happened to appear first in iteration order, sending the request with the wrong API key. The upstream then returns a 503 / 404 that looks like a model-availability problem but is actually a credential-misroute.The fix splits the sub-name off
requested_providerand threads it through_try_resolve_from_custom_pool(..., provider_name=<subname>)so name-based pool selection wins over the url-only fallback. The kwarg form mirrors the sibling pool-lookup site in the same function (the named-custom-provider path) that already passes the name correctly viacustom_provider.get(\"name\").This PR salvages the auth-only subset of #29893 (credit @vanhci). The original PR was closed with CHANGES_REQUESTED because it mixed in unrelated WhatsApp /
uv pip --system/ i18n flag changes; per reviewer feedback ("split it into individual PRs"), this PR ships only thehermes_cli/runtime_provider.pyfix plus its regression tests. Two small refinements over the original diff: the sub-name is passed as a keyword argument (provider_name=) instead of as the third positional (which would have aliased toapi_mode_override), and.strip()is applied so trailing whitespace in the alias name does not break pool selection.Related Issue
Fixes #29872
Type of Change
Changes Made
hermes_cli/runtime_provider.py— in_resolve_named_custom_runtime, extract<subname>fromrequested_provider(e.g.bobapi-deepseekfromcustom:bobapi-deepseek) and thread it through_try_resolve_from_custom_pool(..., provider_name=_pool_provider_name)for the direct-alias bare-custom branch.tests/hermes_cli/test_runtime_provider_resolution.py— added three regression tests:test_direct_alias_custom_subname_passed_to_pool_lookup— asserts the sub-name reaches the pool lookup as theprovider_name=kwarg.test_direct_alias_bare_custom_keeps_provider_name_none— baseline guard so bareprovider: custom(no sub-name) still passesNone.test_direct_alias_subname_resolves_correct_pool_when_url_shared— end-to-end check: twocustom_providersbehind onebase_url, the deepseek alias must select the deepseek pool, not whichever entry iterates first.How to Test
custom_providerssharing the samebase_url, each with a distinctkey_env, plus amodel_aliases:entry that references one by sub-name (the exact reproduction recipe is in [Bug]: model_aliases provider: custom:<subname> normalized to bare 'custom', wrong API key picked from pool #29872).hermes chat -m <alias>(without the--providerworkaround) and confirm the request fires with the alias's intended key instead of whichevercustom_providersentry appears first.Regression-verified the new tests by temporarily reverting the prod change (
git stash push hermes_cli/runtime_provider.py) — two of the three new tests fail with the unfixed code (third stays green because it covers the bare-custom baseline). With the fix restored, all three pass.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AFor New Skills
N/A
Screenshots / Logs
Targeted-suite output:
```text
$ uv run --extra dev python -m pytest tests/hermes_cli/test_runtime_provider_resolution.py -q
........................................................................ [ 55%]
......................................................... [100%]
129 passed in 3.10s
$ uv run --extra dev ruff check hermes_cli/runtime_provider.py tests/hermes_cli/test_runtime_provider_resolution.py
All checks passed!
$ git diff main --check
(no whitespace issues)
```
Regression check (prod fix temporarily reverted) — confirms the new tests catch the bug:
```text
$ git stash push hermes_cli/runtime_provider.py
$ pytest -k "direct_alias_custom_subname or direct_alias_subname_resolves" -q
FAILED ...::test_direct_alias_custom_subname_passed_to_pool_lookup
FAILED ...::test_direct_alias_subname_resolves_correct_pool_when_url_shared
AssertionError: alias sub-name must select the deepseek pool, not the first url match
2 failed, 1 passed
```
Made with Cursor