Skip to content

[Bug]: Model Switcher: Configuration Persistence Loss & Missing Pre-fills for Custom Endpoints #3263

@bb873

Description

@bb873

Bug Description

Description

The current model-switching logic in hermes_cli makes it difficult to maintain a stable connection to local/custom OpenAI-compatible endpoints (e.g., Ollama, LM Studio). Even after successfully configuring a custom endpoint, the CLI frequently "forgets" these settings, defaults back to cloud providers (like OpenRouter) due to environment variable precedence, and requires manual re-entry of the Base URL and API Key in every interactive session.

Current Behavior

  1. No Pre-filling: Running hermes model and selecting "Custom endpoint" always presents an empty prompt for the API base URL, ignoring existing values stored in ~/.hermes/config.yaml.
  2. Environment Overrides: If an OPENROUTER_API_KEY exists in the .env file, the runtime_provider logic often prioritizes it over a manually configured model.base_url, causing the "Active Provider" to flip back to OpenRouter unexpectedly.
  3. Silent Validation Failures: In hermes_cli/model_switch.py, the validation step uses a broad try/except block that returns success=True even when a connection to a local endpoint fails (e.g., connection refused or 404). This prevents users from seeing why their custom setup isn't working.

Steps to Reproduce

Steps to Reproduce

  1. Run hermes model and configure a custom URL (e.g., http://localhost:11434/v1).
  2. Verify the model works in a chat session.
  3. Exit the CLI and run hermes model again.
  4. Observe that the "Active provider" has reverted

Expected Behavior

Expected Behavior

  1. Interactive Pre-filling: When running hermes model and selecting "Custom endpoint," the CLI should pull the existing base_url and api_key from ~/.hermes/config.yaml and offer them as default values in the prompt (e.g., API base URL [http://localhost:11434/v1]: ).
  2. Configuration Precedence: An explicitly configured model.base_url in config.yaml should take precedence over environment-level API keys (like OPENROUTER_API_KEY) when the active provider is set to custom.
  3. Transparent Validation: The model-switching pipeline should surface connection errors (e.g., "Connection Refused," "401 Unauthorized," or "Model Not Found") instead of catching exceptions and returning a silent success=True.
  4. Persistent State: A successfully configured custom model should remain the "Active provider" across CLI restarts unless explicitly changed by the user.

Actual Behavior

Actual Behavior

  1. Empty Interactive Prompts: Running hermes model and selecting "Custom endpoint" always presents a blank prompt for API base URL and API Key, even when these values are already correctly defined in ~/.hermes/config.yaml.
  2. Environment Variable Precedence: If an OPENROUTER_API_KEY is present in the .env file, the runtime_provider logic defaults the "Active Provider" to OpenRouter, effectively "losing" the custom endpoint configuration in the UI.
  3. Silent Validation Failures: The switch_model pipeline in hermes_cli/model_switch.py uses a broad try/except block that returns success=True for almost any connection error (e.g., 404, Connection Refused, or invalid JSON). This results in the CLI reporting a "Successful switch" to a model that is actually unreachable.
  4. Non-Persistent State: Because the interactive flow does not check or display the current configuration as a default, users are forced to re-type the full Base URL and API Key every time they wish to verify or tweak their custom setup.

Affected Component

CLI (interactive chat)

Messaging Platform (if gateway-related)

No response

Operating System

macOS Tahoe 26.2 (25C56)

Python Version

3.9.6

Hermes Version

Hermes Agent v0.4.0 (2026.3.23)

Relevant Logs / Traceback

Root Cause Analysis (optional)

The issue stems from a combination of silent error handling in the switching pipeline and a lack of state-awareness in the CLI entry point.

1. Silent Validation Failures

File: hermes_cli/model_switch.py
Lines: ~110-120
The validation step catches all exceptions (network timeouts, 401s, 404s) and returns a "success" state with recognized: False. This prevents the caller from knowing the switch failed technically.

try:
    validation = validate_requested_model(...)
except Exception:
    # This block effectively hides all connection/config issues
    validation = {
        "accepted": True,
        "persist": True,
        "recognized": False,
        "message": None,
    }

### Proposed Fix (optional)

### **Proposed Fixes**

#### **1. Surface Validation Errors**
**File:** `hermes_cli/model_switch.py`  
Update Step 5 to return the actual error message from the exception so the user knows why the connection failed (e.g., 404 or Connection Refused).

```python
# Proposed change
try:
    validation = validate_requested_model(
        new_model,
        target_provider,
        api_key=api_key,
        base_url=base_url,
    )
except Exception as e:
    # Stop returning "accepted: True" for failed connections
    return ModelSwitchResult(
        success=False,
        new_model=new_model,
        target_provider=target_provider,
        error_message=f"Connection failed: {str(e)}",
    )

### Are you willing to submit a PR for this?

- [ ] I'd like to fix this myself and submit a PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions