hermes - ✅(Solved) Fix CLI doesn't honor user-defined providers via chat --provider or -m <alias> [1 pull requests, 1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#16767Fetched 2026-04-28 06:50:51
View on GitHub
Comments
1
Participants
1
Timeline
7
Reactions
0
Author
Participants
Timeline (top)
labeled ×4cross-referenced ×2commented ×1

User-defined providers configured under the providers: dict in ~/.hermes/config.yaml are reachable through some CLI paths (top-level oneshot, TUI /model, delegation:) but blocked or ignored by two heavily-used paths:

  1. hermes chat --provider <user-name> is rejected by argparse choices=.
  2. -m <alias> from the CLI doesn't propagate the alias's base_url (only the model name).

Both result in requests silently routing to whatever model.base_url is in config, instead of the user-intended endpoint — verified via log inspection on the targeted endpoint.

Error Message

$ hermes chat --provider host-b -m gemma-4-31b -q "Hello" hermes chat: error: argument --provider: invalid choice: 'host-b' (choose from 'auto', 'openrouter', 'nous', 'openai-codex', ...)

Root Cause

Root cause: hermes_cli/main.py ~L7765 — chat subcommand argparse hardcodes choices= to built-in providers only. The top-level hermes --provider … flag does NOT have this restriction and routes correctly.

Fix Action

Fix / Workaround

Workarounds verified to work today

PR fix notes

PR #16811: Fix CLI routing for user-defined providers (closes #16767)

Description (problem / solution / changelog)

Closes #16767.

User-defined providers configured in ~/.hermes/config.yaml (providers: dict + model_aliases:) were silently dropped by two CLI paths, so requests routed to the wrong endpoint. This PR fixes both paths and adds regression tests.

Commits

  • fix(cli): honor user-defined providers via chat --provider and -m <alias> (4 files, +65/−39):

    • hermes_cli/model_switch.py — root cause: _ensure_direct_aliases() rebound DIRECT_ALIASES to a freshly-loaded dict, leaving every from … import DIRECT_ALIASES caller stuck on the stale empty original. Switched to .update() so module attribute references stay valid.
    • hermes_cli/main.py — chat subcommand --provider had choices=[…] hardcoded to built-in providers, rejecting valid keys from user providers: config. Dropped the choices list; runtime resolution validates correctly downstream.
    • hermes_cli/oneshot.py-m <alias> only resolved the model name; the alias's base_url was never propagated. Now consults DIRECT_ALIASES before falling through to detect_provider_for_model, and threads the alias's base_url to resolve_runtime_provider(explicit_base_url=…).
    • hermes_cli/runtime_provider.py_resolve_named_custom_runtime now honors (provider="custom", explicit_base_url=…) so a base_url propagated from a direct-alias resolution actually builds a runtime instead of falling through to provider-registry handlers that don't know about ad-hoc local endpoints.
  • test(cli): regression coverage for user-provider routing fix (#16767) (1 file, +58):

    • test_ensure_direct_aliases_mutates_in_place — captures id(DIRECT_ALIASES) before/after _ensure_direct_aliases() and asserts it's unchanged. Catches the rebinding regression specifically.
    • test_chat_provider_argparse_acceptance — invokes main() with chat --provider my-custom-key and asserts no SystemExit plus the value propagates. Catches the choices-list regression.
    • test_resolve_named_custom_runtime_honors_explicit_base_url — calls the function with provider="custom" + an explicit base_url and asserts the returned dict has the right base_url and source == "direct-alias".

Test plan

  • pytest -q tests/hermes_cli/test_regression_16767.py passes on this branch (3/3 in ~1.4 s)
  • Same tests fail when only the four source files are reverted to main (test file kept) — produces the exact argparse error / staleness symptoms the bugs cause, confirming each test catches its target regression
  • Verified end-to-end live: hermes chat --provider <user-defined-key> -m <model> -q "…" and hermes -m <user-alias> -z "…" both reach the user-intended endpoint after the fix, observable via the target server's request log

Notes

  • No new dependencies, no schema changes, no upstream behavior change for any existing CLI invocation that already worked.
  • The TUI /model command path is unaffected (it already worked correctly via a different code path).
  • See the original issue #16767 for the reproducible setup and the per-bug root-cause analysis.

Changed files

  • hermes_cli/main.py (modified, +5/-25)
  • hermes_cli/model_switch.py (modified, +8/-3)
  • hermes_cli/oneshot.py (modified, +28/-11)
  • hermes_cli/runtime_provider.py (modified, +24/-0)
  • tests/hermes_cli/test_regression_16767.py (added, +58/-0)

Code Example

model:
  default: "gemma-4-26b"
  provider: "custom"
  base_url: "http://localhost:8080/v1"

providers:
  host-a:
    api: "http://localhost:8080/v1"
    default_model: "gemma-4-26b"
    transport: "openai"
  host-b:
    api: "http://localhost:11435/v1"
    default_model: "gemma-4-31b"
    transport: "openai"

model_aliases:
  my-31b:
    model: "gemma-4-31b"
    provider: "custom"
    base_url: "http://localhost:11435/v1"

---

$ hermes chat --provider host-b -m gemma-4-31b -q "Hello"
hermes chat: error: argument --provider: invalid choice: 'host-b'
  (choose from 'auto', 'openrouter', 'nous', 'openai-codex', ...)

---

$ hermes -m my-31b -z "Hello"

---

direct = DIRECT_ALIASES.get(key)
   if direct is not None:
       return (direct.provider, direct.model, key)   # drops direct.base_url
RAW_BUFFERClick to expand / collapse

Summary

User-defined providers configured under the providers: dict in ~/.hermes/config.yaml are reachable through some CLI paths (top-level oneshot, TUI /model, delegation:) but blocked or ignored by two heavily-used paths:

  1. hermes chat --provider <user-name> is rejected by argparse choices=.
  2. -m <alias> from the CLI doesn't propagate the alias's base_url (only the model name).

Both result in requests silently routing to whatever model.base_url is in config, instead of the user-intended endpoint — verified via log inspection on the targeted endpoint.

Reproducible setup (~/.hermes/config.yaml)

model:
  default: "gemma-4-26b"
  provider: "custom"
  base_url: "http://localhost:8080/v1"

providers:
  host-a:
    api: "http://localhost:8080/v1"
    default_model: "gemma-4-26b"
    transport: "openai"
  host-b:
    api: "http://localhost:11435/v1"
    default_model: "gemma-4-31b"
    transport: "openai"

model_aliases:
  my-31b:
    model: "gemma-4-31b"
    provider: "custom"
    base_url: "http://localhost:11435/v1"

(Two local llama.cpp instances on different ports — common multi-model pattern.)

Bug 1 — hermes chat --provider <user-name> rejected by argparse

$ hermes chat --provider host-b -m gemma-4-31b -q "Hello"
hermes chat: error: argument --provider: invalid choice: 'host-b'
  (choose from 'auto', 'openrouter', 'nous', 'openai-codex', ...)

Expected: routes through _resolve_named_custom_runtime (which already handles user providers:) when --provider matches a key in user config.

Root cause: hermes_cli/main.py ~L7765 — chat subcommand argparse hardcodes choices= to built-in providers only. The top-level hermes --provider … flag does NOT have this restriction and routes correctly.

Suggested fix: drop choices= on the chat subparser's --provider, or build the list at runtime as BUILTIN_PROVIDERS | set(config.get("providers", {}).keys()).

Bug 2 — -m <alias> doesn't propagate base_url

$ hermes -m my-31b -z "Hello"

Request silently goes to model.base_url (port 8080) with the literal string my-31b as the model name. Verified by tailing the llama.cpp log on each port — port 11435 records no activity, port 8080 receives the request.

Expected: alias resolves to (model=gemma-4-31b, provider=custom, base_url=http://localhost:11435/v1) and the request hits port 11435.

Root cause (two layers):

  1. hermes_cli/models.py:1537detect_provider_for_model() only searches built-in static catalogs and the OpenRouter catalog. It does not consult user providers: or DIRECT_ALIASES.

  2. hermes_cli/model_switch.py:434–436resolve_alias() returns a 3-tuple that drops base_url:

    direct = DIRECT_ALIASES.get(key)
    if direct is not None:
        return (direct.provider, direct.model, key)   # drops direct.base_url

    Callers in hermes_cli/oneshot.py therefore can't thread base_url through to resolve_runtime_provider(explicit_base_url=...).

Suggested fix: extend detect_provider_for_model() to consult DIRECT_ALIASES (matched by alias name or default_model), and have resolve_alias() return a 4-tuple (or DirectAlias) including base_url; update CLI callers to pass it through.

Workarounds verified to work today

  • hermes --provider host-b -m gemma-4-31b -z "…" (top-level oneshot, no chat subcommand) ✓
  • TUI /model my-31b (uses a separate path that handles base_url correctly) ✓
  • For multi-model parallel use, delegation.base_url works as designed (tools/delegate_tool.py:2178) ✓

Environment

  • Hermes Agent v0.11.0 (2026.4.23)
  • Python 3.11.14, uv-managed venv
  • Linux / Ubuntu
  • Two local llama.cpp servers (Gemma 4 26B and Gemma 4 31B) on different ports

extent analysis

TL;DR

The most likely fix involves modifying the hermes_cli/main.py and hermes_cli/models.py files to properly handle user-defined providers and model aliases.

Guidance

  • Modify the hermes_cli/main.py file to drop the choices= parameter on the chat subparser's --provider flag or build the list at runtime using BUILTIN_PROVIDERS | set(config.get("providers", {}).keys()).
  • Update the hermes_cli/models.py file to extend the detect_provider_for_model() function to consult user-defined providers: and DIRECT_ALIASES.
  • Modify the hermes_cli/model_switch.py file to have the resolve_alias() function return a 4-tuple (or DirectAlias) including base_url.
  • Update CLI callers to pass the base_url through to resolve_runtime_provider(explicit_base_url=...).

Example

# hermes_cli/main.py
parser.add_argument('--provider', choices=list(BUILTIN_PROVIDERS) + list(config.get("providers", {}).keys()))

# hermes_cli/models.py
def detect_provider_for_model(model_name):
    # ... existing code ...
    for provider, config in config.get("providers", {}).items():
        if config.get("default_model") == model_name:
            return provider
    # ... existing code ...

# hermes_cli/model_switch.py
def resolve_alias(key):
    direct = DIRECT_ALIASES.get(key)
    if direct is not None:
        return (direct.provider, direct.model, direct.base_url, key)

Notes

The provided fixes assume that the issue is due to the hardcoded choices= parameter and the lack of consultation of user-defined providers: and DIRECT_ALIASES in the detect_provider_for_model() function.

Recommendation

Apply the suggested workarounds, such as using the top-level oneshot command or

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING