NousResearch/hermes-agent

Custom providers model field not propagated from config

Cluster 7828. Data as of

Nothing open on GitHub. Fix merged in #7935 on 11 Apr 2026. Check it covers your case.

The short answer

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

One bug report describes the custom_providers model field not being preserved, leading to "Model Not Exist" errors. Two pull requests with identical titles propose propagating the model field from config so the API receives the correct model name.

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.

  • #7935 fix(custom-providers): propagate model field from config so API receives the correct model name

    merged to main

    files: cli.py, hermes_cli/config.py, hermes_cli/runtime_provider.py, tests/hermes_cli/test_runtime_provider_resolution.py

    Show the diff
    • cli.py
      @@ -2710,6 +2710,15 @@ def _ensure_runtime_credentials(self) -> bool:
               self.api_key = [redacted:secret-assignment]
               self.base_url = base_url
       
      +        # When a custom_provider entry carries an explicit `model` field,
      +        # use it as the effective model name.  Without this, running
      +        # `hermes chat --model <provider-name>` sends the provider name
      +        # (e.g. "my-provider") as the model string to the API instead of
      +        # the configured model (e.g. "qwen3.6-plus"), causing 400 errors.
      +        runtime_model = runtime.get("model")
      +        if runtime_model and isinstance(runtime_model, str):
      +            self.model = runtime_model
      +
               # Normalize model for the resolved provider (e.g. swap non-Codex
               # models when provider is openai-codex).  Fixes #651.
               model_changed = self._normalize_model_for_provider(resolved_provider)
    • hermes_cli/config.py
      @@ -1497,7 +1497,7 @@ def check_config_version() -> Tuple[int, int]:
       
       # Valid fields inside a custom_providers list entry
       _VALID_CUSTOM_PROVIDER_FIELDS = {
      -    "name", "base_url", "api_key", "api_mode", "models",
      +    "name", "base_url", "api_key", "api_mode", "model", "models",
           "context_length", "rate_limit_delay",
       }
       
    • hermes_cli/runtime_provider.py
      @@ -304,6 +304,9 @@ def _get_named_custom_provider(requested_provider: str) -> Optional[Dict[str, An
               api_mode = _parse_api_mode(entry.get("api_mode"))
               if api_mode:
                   result["api_mode"] = api_mode
      +        model_name = str(entry.get("model", "") or "").strip()
      +        if model_name:
      +            result["model"] = model_name
               return result
       
           return None
      @@ -329,6 +332,11 @@ def _resolve_named_custom_runtime(
           # Check if a credential pool exists for this custom endpoint
           pool_result = _try_resolve_from_custom_pool(base_url, "custom", custom_provider.get("api_mode"))
           if pool_result:
      +        # Propagate the model name even when using pooled credentials —
      +        # the pool doesn't know about the custom_providers model field.
      +        model_name = custom_provider.get("model")
      +        if model_name:
      +            pool_result["model"] = model_name
               return pool_result
       
           api_key_candidates = [
      @@ -339,7 +347,7 @@ def _resolve_named_custom_runtime(
           ]
           api_key = [redacted:secret-assignment] for candidate in api_key_candidates if has_usable_secret(candidate)), "")
       
      -    return {
      +    result = {
               "provider": "custom",
               "api_mode": custom_provider.get("api_mode")
               or _detect_api_mode_for_url(base_url)
      @@ -348,6 +356,11 @@ def _resolve_named_custom_runtime(
               "api_key": [redacted:secret-assignment] or "no-key-required",
               "source": f"custom_provider:{custom_provider.get('name', requested_provider)}",
           }
      +    # Propagate the model name so callers can override self.model when the
      +    # provider name differs from the actual model string the API expects.
      +    if custom_provider.get("model"):
      +        result["model"] = custom_provider["model"]
      +    return result
       
       
       def _resolve_openrouter_runtime(
    • tests/hermes_cli/test_runtime_provider_resolution.py
      @@ -1214,3 +1214,115 @@ def test_openrouter_provider_not_affected_by_custom_fix(monkeypatch):
       
           resolved = rp.resolve_runtime_provider(requested="openrouter")
           assert resolved["provider"] == "openrouter"
      +
      +
      +# ------------------------------------------------------------------
      +# fix #7828 — custom_providers model field must propagate to runtime
      +# ------------------------------------------------------------------
      +
      +
      +def test_get_named_custom_provider_includes_model(monkeypatch):
      +    """_get_named_custom_provider should include the model field from config."""
      +    monkeypatch.setattr(rp, "load_config", lambda: {
      +        "custom_providers": [{
      +            "name": "my-dashscope",
      +            "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
      +            "api_key": "[redacted:secret-assignment]",
      +            "api_mode": "chat_completions",
      +            "model": "qwen3.6-plus",
      +        }],
      +    })
      +
      +    result = rp._get_named_custom_provider("my-dashscope")
      +    assert result is not None
      +    assert result["model"] == "qwen3.6-plus"
      +
      +
      +def test_get_named_custom_provider_excludes_empty_model(monkeypatch):
      +    """Empty or whitespace-only model field should not appear in result."""
      +    for model_val in ["", "   ", None]:
      +        entry = {
      +            "name": "test-ep",
      +            "base_url": "https://example.com/v1",
      +            "api_key": "key",
      +        }
      +        if model_val is not None:
      +            entry["model"] = model_val
      +
      +        monkeypatch.setattr(rp, "load_config", lambda e=entry: {
      +            "custom_providers": [e],
      +        })
      +
      +        result = rp._get_named_custom_provider("test-ep")
      +        assert result is not None
      +        assert "model" not in result, (
      +            f"model field {model_val!r} should not be included in result"
      +        )
      +
      +
      +def test_named_custom_runtime_propagates_model_direct_path(monkeypatch):
      +    """Model should propagate through the direct (non-pool) resolution path."""
      +    monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "my-server")
      +    monkeypatch.setattr(
      +        rp, "_get_named_custom_provider",
      +        lambda p: {
      +            "name": "my-server",
      +            "base_url": "http://localhost:8000/v1",
      +            "api_key": "[redacted:secret-assignment]",
      +            "model": "qwen3.6-plus",
      +        },
      +    )
      +    # Ensure pool doesn't intercept
      +    monkeypatch.setattr(rp, "_try_resolve_from_custom_pool", lambda *a, **k: None)
      +
      +    resolved = rp.resolve_runtime_provider(requested="my-server")
      +    assert resolved["model"] == "qwen3.6-plus"
      +    assert resolved["provider"] == "custom"
      +
      +
      +def test_named_custom_runtime_propagates_model_pool_path(monkeypatch):
      +    """Model should propagate even when credential pool handles credentials."""
      +    monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "my-server")
      +    monkeypatch.setattr(
      +        rp, "_get_named_custom_provider",
      +        lambda p: {
      +            "name": "my-server",
      +            "base_url": "http://localhost:8000/v1",
      +            "api_key": "[redacted:secret-assignment]",
      +            "model": "qwen3.6-plus",
      +        },
      +    )
      +    # Pool returns a result (intercepting the normal path)
      +    monkeypatch.setattr(
      +        rp, "_try_resolve_from_custom_pool",
      +        lambda *a, **k: {
      +            "provider": "custom",
      +            "api_mode": "chat_completions",
      +            "base_url": "http://localhost:8000/v1",
      +            "api_key": "[redacted:secret-assignment]",
      +            "source": "pool:custom:my-server",
      +        },
      +    )
      +
      +    resolved = rp.resolve_runtime_provider(requested="my-server")
      +    assert resolved["model"] == "qwen3.6-plus", (
      +        "model must be injected into pool result"
      +    )
      +    assert resolved["api_key"] == "pool-key", "pool credentials should be used"
      +
      +
      +def test_named_custom_runtime_no_model_when_absent(monkeypatch):
      +    """When custom_providers entry has no model field, runtime should not either."""
      +    monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "my-server")
      +    monkeypatch.setattr(
      +        rp, "_get_named_custom_provider",
      +        lambda p: {
      +            "name": "my-server",
      +            "base_url": "http://localhost:8000/v1",
      +            "api_key": "[redacted:secret-assignment]",
      +        },
      +    )
      +    monkeypatch.setattr(rp, "_try_resolve_from_custom_pool", lambda *a, **k: None)
      +
      +    resolved = rp.resolve_runtime_provider(requested="my-server")
      +    assert "model" not in resolved

1 PR closed without merging

From the close comments: merged via another change 1

  • #7916 fix(custom-providers): propagate model field from config so API receives the correct model name · merged via another change → #7935
Closed issues · 1
  • Closed#7828[Bug]: custom_providers model field not preserved, causing "Model Not Exist" errors
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.67: 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
#7935
Closed issues
1
Attempts
1 PR closed without merging
Fix link checked
11 Apr 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.

For your agent

The same facts, typed.