Fixes for bare 'custom' slug written to model.provider

NousResearch/hermes-agent · cluster 17478

Data as of

Nothing open on GitHub. Fix merged in #17891. Check it covers your case.

The short answer

0open issues
0open PRs
5items grouped
Known?
Yes. 5 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #17891. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #17891. That is separate from the fix event.
Still open
Nothing open on GitHub.

Multiple fixes and a bug report addressing an issue where the model picker writes the literal string 'custom' as the provider slug instead of the actual provider name. Changes span CLI, model-switch, and related resolution logic.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #17891 fix(model_switch): prevent bare 'custom' slug in model.provider (#17478)

    merged to main

    files: hermes_cli/model_switch.py, hermes_cli/providers.py, scripts/release.py, tests/hermes_cli/test_model_switch_custom_providers.py

    Show the diff
    • hermes_cli/model_switch.py
      @@ -1501,7 +1501,14 @@ def _record_builtin_endpoint(slug: str) -> None:
                           current_base_url
                           and api_url == current_base_url.strip().rstrip("/")
                       ):
      -                    slug = current_provider or custom_provider_slug(display_name)
      +                    # Guard against bare "custom" slug left by a prior
      +                    # failed switch — always resolve to the canonical
      +                    # custom:<name> form.  (GH #17478)
      +                    slug = (
      +                        current_provider
      +                        if current_provider and current_provider != "custom"
      +                        else custom_provider_slug(display_name)
      +                    )
                       else:
                           slug = custom_provider_slug(display_name)
                       groups[group_key] = {
    • hermes_cli/providers.py
      @@ -585,6 +585,12 @@ def resolve_custom_provider(
           if not requested:
               return None
       
      +    # If the stored provider is the bare string "custom" (corrupt state
      +    # from a prior model-switch bug), fall back to the first custom
      +    # provider entry so existing configs self-heal.  (GH #17478)
      +    bare_custom_fallback = requested == "custom"
      +    first_valid = None
      +
           for entry in custom_providers:
               if not isinstance(entry, dict):
                   continue
      @@ -599,6 +605,10 @@ def resolve_custom_provider(
               if not display_name or not api_url:
                   continue
       
      +        # Stash the first valid entry for bare-"custom" fallback
      +        if first_valid is None:
      +            first_valid = (display_name, api_url)
      +
               slug = custom_provider_slug(display_name)
               if requested not in {display_name.lower(), slug}:
                   continue
      @@ -614,6 +624,21 @@ def resolve_custom_provider(
                   source="user-config",
               )
       
      +    # Self-heal: bare "custom" matched nothing — return first valid entry
      +    if bare_custom_fallback and first_valid:
      +        dname, aurl = first_valid
      +        slug = custom_provider_slug(dname)
      +        return ProviderDef(
      +            id=slug,
      +            name=dname,
      +            transport="openai_chat",
      +            api_key_env_vars=(),
      +            base_url=aurl,
      +            is_aggregator=False,
      +            auth_type="api_key",
      +            source="user-config",
      +        )
      +
           return None
       
       
    • scripts/release.py
      @@ -349,6 +349,7 @@
           "[redacted:email]": "houziershi",
           "[redacted:email]": "PStarH",
           "[redacted:email]": "Sanjays2402",
      +    "[redacted:email]": "Andy283",
           "[redacted:email]": "bingo906",
           "[redacted:email]": "AaronWong1999",
           "[redacted:email]": "DeployFaith",
    • tests/hermes_cli/test_model_switch_custom_providers.py
      @@ -296,12 +296,13 @@ def test_list_authenticated_providers_groups_same_endpoint(monkeypatch):
       def test_list_authenticated_providers_current_endpoint_uses_current_slug(monkeypatch):
           """When current_base_url matches the grouped endpoint, the slug must
           equal current_provider so picker selection routes through the live
      -    credential pipeline."""
      +    credential pipeline — provided current_provider is a real slug, not
      +    the corrupt bare "custom" (see #17478)."""
           monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {})
           monkeypatch.setattr(providers_mod, "HERMES_OVERLAYS", {})
       
           providers = list_authenticated_providers(
      -        current_provider="custom",
      +        current_provider="custom:ollama",
               current_base_url="http://localhost:11434/v1",
               user_providers={},
               custom_providers=[
      @@ -314,10 +315,36 @@ def test_list_authenticated_providers_current_endpoint_uses_current_slug(monkeyp
           matches = [p for p in providers if p.get("is_user_defined")]
           assert len(matches) == 1
           group = matches[0]
      -    assert group["slug"] == "custom"
      +    assert group["slug"] == "custom:ollama"
           assert group["is_current"] is True
       
       
      +def test_list_authenticated_providers_bare_custom_slug_recovers(monkeypatch):
      +    """Regression for #17478: when a prior failed switch left the bare
      +    literal "custom" in model.provider, the picker must NOT propagate
      +    that broken slug. It must fall back to the canonical
      +    ``custom:<name>`` form so the picker stays usable."""
      +    monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {})
      +    monkeypatch.setattr(providers_mod, "HERMES_OVERLAYS", {})
      +
      +    providers = list_authenticated_providers(
      +        current_provider="custom",
      +        current_base_url="http://localhost:11434/v1",
      +        user_providers={},
      +        custom_providers=[
      +            {"name": "Ollama — GLM 5.1", "base_url": "http://localhost:11434/v1",
      +             "api_key": "ollama", "model": "glm-5.1"},
      +        ],
      +        max_models=50,
      +    )
      +
      +    matches = [p for p in providers if p.get("is_user_defined")]
      +    assert len(matches) == 1
      +    group = matches[0]
      +    # Canonical slug, NOT the bare "custom" that caused #17478
      +    assert group["slug"] == "custom:ollama"
      +
      +
       def test_list_authenticated_providers_distinct_endpoints_stay_separate(monkeypatch):
           """Entries with different base_urls must produce separate picker rows
           even if some display names happen to be similar."""

3 PRs closed without merging

From the close comments: superseded by another PR 2 · merged via another change 1

  • #17481 fix: prevent bare 'custom' slug in model.provider (#17478) · merged via another change → 201f7ca, 61fec76, 3fc4c63, #17891
  • #17580 fix(cli): reject literal 'custom' as provider slug in model picker · superseded by another PR → #17481
  • #17671 fix(model-switch): skip "custom"/"local" sentinel when assigning picker slug (#17478) · superseded by another PR → #17481
Closed issues · 1
  • Closed#17478bug: picker writes literal 'custom' to model.provider instead of actual provider name
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.40: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#17891
Closed issues
1
Attempts
3 PRs closed without merging
Fix link checked
30 Apr 2026

For your agent

The same facts, typed.