hermes - ✅(Solved) Fix bug: picker writes literal 'custom' to model.provider instead of actual provider name [3 pull requests, 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#17478Fetched 2026-04-30 06:47:14
View on GitHub
Comments
0
Participants
1
Timeline
7
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×3labeled ×3referenced ×1

When using the hermes model interactive picker to switch to a model under custom_providers:, the CLI can write the literal string custom to model.provider in config.yaml instead of the actual provider name (e.g. xiaomi-coding).

This causes every subsequent session to fail with:

✗ Unknown provider 'custom'. Check 'hermes model' for available providers, or define it in config.yaml under 'providers:'.

Error Message

  • Returns None → error at model_switch.py:654

Root Cause

In hermes_cli/model_switch.py list_authenticated_providers() (lines 1466-1472), slug assignment logic:

if current_base_url and api_url == current_base_url.strip().rstrip('/'):
    slug = current_provider or custom_provider_slug(display_name)
else:
    slug = custom_provider_slug(display_name)

When a prior failed switch already wrote provider: custom to config.yaml, current_provider = 'custom'. On the next picker run, if current_base_url matches, slug = current_provider = 'custom' (literal). The picker passes 'custom' as explicit_provider to switch_model().

Then resolve_provider_full('custom', ...) checks:

  • Built-in providers → no match
  • providers: dict → empty, no match
  • custom_providers → slug is custom:xiaomi-coding, doesn't match bare 'custom'
  • Returns None → error at model_switch.py:654

Fix Action

Workaround

hermes config set model.provider <actual-provider-name>

PR fix notes

PR #17481: fix: prevent bare 'custom' slug in model.provider (#17478)

Description (problem / solution / changelog)

Problem

When using hermes model interactive picker to switch to a custom_providers entry, the CLI can write the literal string custom to model.provider in config.yaml instead of the actual provider name (e.g. xiaomi-coding).

This causes every subsequent session to fail with:

Unknown provider "custom". Check hermes model for available providers.

Root cause: in list_authenticated_providers() (model_switch.py:1466-1472), when current_provider is already "custom" from a prior failed switch and current_base_url matches, slug gets set to the literal "custom". Then resolve_provider_full("custom") finds no match since the canonical slug is custom:xiaomi-coding.

Fix (2 files, +33/-1)

hermes_cli/model_switch.py — Slug assignment guards against bare "custom":

  • If current_provider is "custom", fall back to custom_provider_slug(display_name) which produces the canonical custom:<name> form

hermes_cli/providers.py — Self-healing fallback in resolve_custom_provider():

  • When requested == "custom" matches nothing by name or slug, fall back to the first valid custom_providers entry so corrupted configs auto-recover

Testing

9/9 test cases pass:

  • Normal resolution by name and slug
  • Bare "custom" self-heals to first entry
  • Unknown provider returns None
  • Slug assignment handles bug case, normal case, None case, and URL mismatch

Closes #17478

Changed files

  • hermes_cli/model_switch.py (modified, +8/-1)
  • hermes_cli/providers.py (modified, +25/-0)

PR #17580: fix(cli): reject literal 'custom' as provider slug in model picker

Description (problem / solution / changelog)

What does this PR do?

Fix the /model interactive picker writing the literal string custom as model.provider in config.yaml when switching to a custom_providers entry. This happens when a prior failed switch already wrote provider: custom — the picker re-uses that stale value instead of the canonical custom:<name> slug. Every subsequent session then fails with Unknown provider 'custom'.

Related Issue

Fixes #17478

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • hermes_cli/model_switch.pylist_authenticated_providers(): Added and current_provider != "custom" guard to the base-URL matching branch (line ~1392), so the stale literal 'custom' value is rejected and the canonical custom:<name> slug is used instead.
  • tests/hermes_cli/test_model_switch_custom_provider.py — new: 4 tests covering:
    • current_provider='custom' with matching base_url → slug is custom:xiaomi-coding
    • current_provider='' with matching base_url → fallthrough to canonical slug
    • current_provider='custom' with non-matching base_url → fallthrough
    • custom_provider_slug() format validation

How to Test

  1. Set up a custom_providers entry in config.yaml (e.g. xiaomi-coding with base_url)
  2. Run /model picker and switch to it — model.provider should be custom:xiaomi-coding, not bare custom
  3. Verify via hermes config get model.provider

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Debian 13 (Docker)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

For New Skills

N/A — this is a bug fix, not a new skill.

Screenshots / Logs

N/A — no UI changes

Changed files

  • gateway/run.py (modified, +51/-21)
  • hermes_cli/model_switch.py (modified, +1/-0)
  • tests/gateway/test_restart_watcher.py (added, +122/-0)
  • tests/hermes_cli/test_model_switch_custom_provider.py (added, +78/-0)

PR #17671: fix(model-switch): skip "custom"/"local" sentinel when assigning picker slug (#17478)

Description (problem / solution / changelog)

Summary

Closes #17478. Fix the hermes model picker so that when model.provider in config.yaml is the literal string custom (or local) — a leftover sentinel from an earlier broken switch — the picker rebuilds the canonical custom:<name> slug instead of writing the sentinel back into config and perpetuating the broken state.

The bug

The reporter's config wedged into:

model:
  provider: custom
  base_url: ''
custom_providers:
  - name: xiaomi-coding
    base_url: https://token-plan-sgp.xiaomimimo.com/v1

Every session then fails with:

✗ Unknown provider 'custom'. Check 'hermes model' for available providers,
  or define it in config.yaml under 'providers:'.

Root cause was in hermes_cli/model_switch.py::list_authenticated_providers. When grouping custom_providers by (base_url, api_key), the slug-assignment branch read:

if current_base_url and api_url == current_base_url.strip().rstrip("/"):
    slug = current_provider or custom_provider_slug(display_name)

That branch is meant to reuse the active provider's slug for the row whose endpoint matches the live session, so picker selection routes back through the credential pipeline that already resolved. But when current_provider == "custom" the truthy fallback skipped custom_provider_slug(display_name) entirely, and "custom" propagated back into config on the next switch.

The fix

Treat "custom" and "local" (case-insensitive, also empty) as sentinels and always rebuild custom:<display-name> from the entry. Real provider IDs like openai-codex or custom:my-shop still hit the live-credential reuse path unchanged.

_current_lc = (current_provider or "").strip().lower()
if (
    current_base_url
    and api_url == current_base_url.strip().rstrip("/")
    and _current_lc not in ("", "custom", "local")
):
    slug = current_provider
else:
    slug = custom_provider_slug(display_name)

switch_model and resolve_provider_full already accept the canonical custom:<name> slug, so a single picker round-trip self-heals the broken config.

Test plan

  • Focused regression: tests/hermes_cli/test_model_switch_custom_providers.py::test_custom_sentinel_provider_recovers_canonical_slug reproduces the reporter's exact provider: custom + xiaomi-coding case and asserts the picker emits custom:xiaomi-coding, not custom.
  • Same coverage for the local sentinel: test_local_sentinel_provider_recovers_canonical_slug.
  • Non-regression for the live-credential reuse path: test_real_provider_still_reused_as_slug_when_endpoints_match confirms custom:my-shop is still preserved when endpoints match.
  • Existing test_list_authenticated_providers_current_endpoint_uses_current_slug updated to use a real provider ID (the prior assertion encoded the buggy behavior — it expected the sentinel to round-trip).
  • Adjacent suites: tests/hermes_cli/test_model_switch_custom_providers.py, tests/hermes_cli/test_user_providers_model_switch.py, tests/hermes_cli/test_overlay_slug_resolution.py — 47 passed.
  • Regression guard: removed the new sentinel guard locally and confirmed the new tests fail with slug == "custom" / slug == "local"; restored and they pass.

Related

  • Fixes #17478

Changed files

  • hermes_cli/model_switch.py (modified, +12/-1)
  • tests/hermes_cli/test_model_switch_custom_providers.py (modified, +95/-3)

Code Example

Unknown provider 'custom'. Check 'hermes model' for available providers, or define it in config.yaml under 'providers:'.

---

if current_base_url and api_url == current_base_url.strip().rstrip('/'):
    slug = current_provider or custom_provider_slug(display_name)
else:
    slug = custom_provider_slug(display_name)

---

model:
  provider: custom          # ← should be 'xiaomi-coding' or 'custom:xiaomi-coding'
  base_url: ''
custom_providers:
  - name: xiaomi-coding
    base_url: https://token-plan-sgp.xiaomimimo.com/v1

---

hermes config set model.provider <actual-provider-name>
RAW_BUFFERClick to expand / collapse

Description

When using the hermes model interactive picker to switch to a model under custom_providers:, the CLI can write the literal string custom to model.provider in config.yaml instead of the actual provider name (e.g. xiaomi-coding).

This causes every subsequent session to fail with:

✗ Unknown provider 'custom'. Check 'hermes model' for available providers, or define it in config.yaml under 'providers:'.

Root Cause

In hermes_cli/model_switch.py list_authenticated_providers() (lines 1466-1472), slug assignment logic:

if current_base_url and api_url == current_base_url.strip().rstrip('/'):
    slug = current_provider or custom_provider_slug(display_name)
else:
    slug = custom_provider_slug(display_name)

When a prior failed switch already wrote provider: custom to config.yaml, current_provider = 'custom'. On the next picker run, if current_base_url matches, slug = current_provider = 'custom' (literal). The picker passes 'custom' as explicit_provider to switch_model().

Then resolve_provider_full('custom', ...) checks:

  • Built-in providers → no match
  • providers: dict → empty, no match
  • custom_providers → slug is custom:xiaomi-coding, doesn't match bare 'custom'
  • Returns None → error at model_switch.py:654

Config State That Triggers It

model:
  provider: custom          # ← should be 'xiaomi-coding' or 'custom:xiaomi-coding'
  base_url: ''
custom_providers:
  - name: xiaomi-coding
    base_url: https://token-plan-sgp.xiaomimimo.com/v1

Suggested Fix

In resolve_provider_full() or the slug assignment logic, either:

  1. Reject bare 'custom' as a valid slug — always require the full custom:<name> format
  2. When current_provider == 'custom', look up the matching custom_provider by base_url and use the proper slug
  3. In startup validation (main.py:1570+), detect provider: custom and auto-resolve to the correct custom_provider entry

Workaround

hermes config set model.provider <actual-provider-name>

Key Source Files

  • hermes_cli/model_switch.pyswitch_model(), list_authenticated_providers()
  • hermes_cli/providers.pyresolve_provider_full(), resolve_custom_provider(), custom_provider_slug()
  • hermes_cli/main.py — startup provider validation (line 1570+)

extent analysis

TL;DR

The issue can be fixed by modifying the resolve_provider_full() function or the slug assignment logic to handle the 'custom' provider name correctly.

Guidance

  • Modify the resolve_provider_full() function to reject bare 'custom' as a valid slug and require the full custom:<name> format.
  • Update the slug assignment logic to look up the matching custom provider by base URL when current_provider == 'custom'.
  • Use the provided workaround by running hermes config set model.provider <actual-provider-name> to set the correct provider name.
  • Review the custom_providers configuration to ensure it is correctly formatted and matches the expected provider names.

Example

No code snippet is provided as the issue is more related to the logic and configuration rather than a specific code block.

Notes

The issue seems to be related to the handling of custom providers in the hermes model. The suggested fixes and workarounds should be applied with caution and tested thoroughly to avoid any unintended consequences.

Recommendation

Apply the workaround by running hermes config set model.provider <actual-provider-name> to set the correct provider name, as this is a straightforward and non-invasive solution that can resolve the issue immediately.

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

hermes - ✅(Solved) Fix bug: picker writes literal 'custom' to model.provider instead of actual provider name [3 pull requests, 1 participants]