# NousResearch/hermes-agent · Model picker filtering and deduplication fixes

## Open now

- Nothing open on GitHub.

These issues and PRs address bugs in the /model picker related to honoring model_catalog.enabled=false settings and deduplicating rows when custom provider endpoints match built-in models.

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #17511. Verify it addresses your case.
- **Recommended:** read PR #17511 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-17511`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: the share of member 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.

## Closed / duplicate issues

- #16970: Bug: Model Picker ignores model_catalog.enabled=false

## Prior & competing PRs

- **#17511: fix(model_switch): dedup /model picker rows when custom provider endpoint matches a built-in (#16970)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `hermes_cli/model_switch.py`, `tests/hermes_cli/test_user_providers_model_switch.py`
  - fix hunks:
  - `hermes_cli/model_switch.py`

    ```diff
    @@ -1018,6 +1018,37 @@ def list_authenticated_providers(
         results: List[dict] = []
         seen_slugs: set = set()  # lowercase-normalized to catch case variants (#9545)
         seen_mdev_ids: set = set()  # prevent duplicate entries for aliases (e.g. kimi-coding + kimi-coding-cn)
    +    # Effective base URLs of every built-in row we emit (normalized lower+rstrip).
    +    # Section 4 uses this to hide ``custom_providers`` entries that point at the
    +    # same endpoint as a built-in (e.g. a user-defined "my-dashscope" on
    +    # https://coding-intl.dashscope.aliyuncs.com/v1 collides with the built-in
    +    # alibaba-coding-plan row when DASHSCOPE_API_KEY is present). Fixes #16970.
    +    _builtin_endpoints: set = set()
    +
    +    def _norm_url(url: str) -> str:
    +        return str(url or "").strip().rstrip("/").lower()
    +
    +    def _record_builtin_endpoint(slug: str) -> None:
    +        """Record the effective base URL for a built-in provider row.
    +
    +        Prefers the live env-override (e.g. DASHSCOPE_BASE_URL) over the
    +        static inference_base_url so the dedup matches what a user typing
    +        that URL into custom_providers would actually hit."""
    +        try:
    +            from hermes_cli.auth import PROVIDER_REGISTRY as _reg
    +        except Exception:
    +            return
    +        pcfg = _reg.get(slug)
    +        if not pcfg:
    +            return
    +        url = ""
    +        if getattr(pcfg, "base_url_env_var", ""):
    +            url = os.environ.get(pcfg.base_url_env_var, "") or ""
    +        if not url:
    +            url = getattr(pcfg, "inference_base_url", "") or ""
    +        normed = _norm_url(url)
    +        if normed:
    +            _builtin_endpoints.add(normed)
     
         data = fetch_models_dev()
     
    ```

    ```diff
    @@ -1124,6 +1155,7 @@ def list_authenticated_providers(
             })
             seen_slugs.add(slug.lower())
             seen_mdev_ids.add(mdev_id)
    +        _record_builtin_endpoint(slug)
     
         # --- 2. Check Hermes-only providers (nous, openai-codex, copilot, opencode-go) ---
         from hermes_cli.providers import HERMES_OVERLAYS
    ```

    ```diff
    @@ -1238,6 +1270,7 @@ def list_authenticated_providers(
             })
             seen_slugs.add(pid.lower())
             seen_slugs.add(hermes_slug.lower())
    +        _record_builtin_endpoint(hermes_slug)
     
         # --- 2b. Cross-check canonical provider list ---
         # Catches providers that are in CANONICAL_PROVIDERS but weren't found
    ```

    ```diff
    @@ -1317,6 +1350,7 @@ def list_authenticated_providers(
                 "source": "canonical",
             })
             seen_slugs.add(_cp.slug.lower())
    +        _record_builtin_endpoint(_cp.slug)
     
         # --- 3. User-defined endpoints from config ---
         # Track (name, base_url) of what section 3 emits so section 4 can skip
    ```

    ```diff
    @@ -1526,6 +1560,15 @@ def list_authenticated_providers(
                 )
                 if _pair_key[0] and _pair_key[1] and _pair_key in _section3_emitted_pairs:
                     continue
    +            # Skip if a built-in row (sections 1/2/2b) already represents this
    +            # endpoint. Fixes #16970: a user-defined "my-dashscope" pointing at
    +            # https://coding-intl.dashscope.aliyuncs.com/v1 duplicates the
    +            # built-in alibaba-coding-plan row whenever DASHSCOPE_API_KEY is
    +            # set. The built-in row carries the curated model list, correct
    +            # auth wiring, and canonical slug — keep it and hide the shadow.
    +            _grp_url_norm = _pair_key[1]
    +            if _grp_url_norm and _grp_url_norm in _builtin_endpoints:
    +                continue
                 results.append({
                     "slug": slug,
                     "name": grp["name"],
    ```
  - `tests/hermes_cli/test_user_providers_model_switch.py`

    ```diff
    @@ -453,6 +453,142 @@ def test_list_authenticated_providers_no_duplicate_labels_across_schemas(monkeyp
         )
     
     
    +def test_list_authenticated_providers_hides_custom_shadowing_builtin_endpoint(monkeypatch):
    +    """#16970: a custom_providers entry whose ``base_url`` matches a built-in
    +    provider's endpoint should be hidden. The built-in row already represents
    +    that endpoint with its canonical slug, curated model list, and auth wiring.
    +
    +    Repro: user sets ``DASHSCOPE_API_KEY`` (triggers the built-in ``alibaba``
    +    row pointing at the static ``inference_base_url``) AND defines a
    +    ``my-alibaba`` custom provider pointing at the same URL. Before the fix,
    +    the picker showed both rows for one endpoint.
    +    """
    +    monkeypatch.setenv("DASHSCOPE_API_KEY", "sk-test")
    +    monkeypatch.setattr(
    +        "agent.models_dev.fetch_models_dev",
    +        lambda: {
    +            "alibaba": {
    +                "name": "Alibaba Cloud (DashScope)",
    +                "env": ["DASHSCOPE_API_KEY"],
    +            }
    +        },
    +    )
    +    monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {})
    +
    +    custom_providers = [
    +        {
    +            "name": "my-alibaba",
    +            # Matches PROVIDER_REGISTRY['alibaba'].inference_base_url exactly.
    +            "base_url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
    +            "api_key": "[redacted:secret-assignment]",
    +            "model": "qwen3.6-plus",
    +            "models": {"qwen3.6-plus": {"context_length": 500000}},
    +        }
    +    ]
    +
    +    providers = list_authenticated_providers(
    +        current_provider="my-alibaba",
    +        user_providers={},
    +        custom_providers=custom_providers,
    +        max_models=50,
    +    )
    +
    +    slugs = [p["slug"] for p in providers]
    +    # Built-in alibaba row should be present.
    +    assert "alibaba" in slugs, (
    +        f"Expected built-in alibaba row, got slugs: {slugs}"
    +    )
    +    # Custom shadow row should be hidden — its base_url matches the built-in's.
    +    assert not any("my-alibaba" in s for s in slugs), (
    +        f"Custom my-alibaba should have been dedup'd against the built-in "
    +        f"alibaba endpoint, got slugs: {slugs}"
    +    )
    +
    +
    +def test_list_authenticated_providers_keeps_custom_with_distinct_endpoint(monkeypatch):
    +    """Dedup must only apply when the endpoint matches a built-in. A custom
    +    provider on a genuinely distinct endpoint stays visible even if a
    +    built-in is also authenticated."""
    +    monkeypatch.setenv("DASHSCOPE_API_KEY", "sk-test")
    +    monkeypatch.setattr(
    +        "agent.models_dev.fetch_models_dev",
    +        lambda: {
    +            "alibaba": {
    +                "name": "Alibaba Cloud (DashScope)",
    +                "env": ["DASHSCOPE_API_KEY"],
    +            }
    +        },
    +    )
    +    monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {})
    +
    +    custom_providers = [
    +        {
    +            "name": "my-private-relay",
    +            "base_url": "https://relay.example.internal/v1",
    +            "api_key": "[redacted:secret-assignment]",
    +            "model": "qwen3.6-plus",
    +            "models": {"qwen3.6-plus": {}},
    +        }
    +    ]
    +
    +    providers = list_authenticated_providers(
    +        current_provider="my-private-relay",
    +        user_providers={},
    +        custom_providers=custom_providers,
    +        max_models=50,
    +    )
    +
    +    slugs = [p["slug"] for p in providers]
    +    assert any("my-private-relay" in s for s in slugs), (
    +        f"Custom provider on distinct endpoint must stay visible, got: {slugs}"
    +    )
    +
    +
    +def test_list_authenticated_providers_dedup_honors_base_url_env_override(monkeypatch):
    +    """The dedup must track the EFFECTIVE endpoint — if DASHSCOPE_BASE_URL
    +    overrides the static inference_base_url, a custom provider pointing at
    +    the overridden URL (not the static one) should still be recognized as
    +    a duplicate."""
    +    monkeypatch.setenv("DASHSCOPE_API_KEY", "sk-test")
    +    monkeypatch.setenv(
    +        "DASHSCOPE_BASE_URL",
    +        "https://custom-dashscope.example.com/v1",
    +    )
    +    monkeypatch.setattr(
    +        "agent.models_dev.fetch_models_dev",
    +        lambda: {
    +            "alibaba": {
    +                "name": "Alibaba Cloud (DashScope)",
    +                "env": ["DASHSCOPE_API_KEY"],
    +            }
    +        },
    +    )
    +    monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {})
    +
    +    custom_providers = [
    +        {
    +            "name": "my-dashscope-override",
    +            # Same URL as DASHSCOPE_BASE_URL env override above.
    +            "base_url": "https://custom-dashscope.example.com/v1",
    +            "api_key": "[redacted:secret-assignment]",
    +            "model": "qwen3.6-plus",
    +        }
    +    ]
    +
    +    providers = list_authenticated_providers(
    +        current_provider="alibaba",
    +        user_providers={},
    +        custom_providers=custom_providers,
    +        max_models=50,
    +    )
    +
    +    slugs = [p["slug"] for p in providers]
    +    assert not any("my-dashscope-override" in s for s in slugs), (
    +        f"Custom entry matching env-overridden built-in endpoint should be "
    +        f"dedup'd, got: {slugs}"
    +    )
    +
    +
     # =============================================================================
     # Tests for _get_named_custom_provider with providers: dict
     # =============================================================================
    ```
- **#16975: fix(cli): respect model_catalog filtering in /model picker**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#17511`
- **#17218: fix(model_switch): honor model_catalog.enabled=false in /model picker (#16970)**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#17511`

## Fix status

- Merged to main in PR #17511 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-04-29`.
