hermes - ✅(Solved) Fix auxiliary.{task} with provider:auto + base_url set falls into 'custom' path with empty api_key, returns 401 [2 pull requests, 4 comments, 4 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#16829Fetched 2026-04-29 06:38:47
View on GitHub
Comments
4
Participants
4
Timeline
11
Reactions
0
Timeline (top)
commented ×4labeled ×4cross-referenced ×2closed ×1

When an auxiliary.<task> block in ~/.hermes/config.yaml has provider: auto (or any value) plus a base_url set, _resolve_task_provider_model() returns provider="custom" with api_key=None. Downstream client construction then makes the API call without an Authorization header, causing the upstream provider (e.g. OpenRouter) to return 401 Missing Authentication header.

This affects any user whose config has the default-looking shape:

auxiliary:
  title_generation:
    provider: openrouter        # or auto
    model: minimax/minimax-m2.7
    base_url: https://openrouter.ai/api/v1
    api_key: ''                 # empty — user expects fallback to OPENROUTER_API_KEY env var

Root Cause

When an auxiliary.<task> block in ~/.hermes/config.yaml has provider: auto (or any value) plus a base_url set, _resolve_task_provider_model() returns provider="custom" with api_key=None. Downstream client construction then makes the API call without an Authorization header, causing the upstream provider (e.g. OpenRouter) to return 401 Missing Authentication header.

This affects any user whose config has the default-looking shape:

auxiliary:
  title_generation:
    provider: openrouter        # or auto
    model: minimax/minimax-m2.7
    base_url: https://openrouter.ai/api/v1
    api_key: ''                 # empty — user expects fallback to OPENROUTER_API_KEY env var

Fix Action

Workaround

Remove both api_key: and base_url: from every auxiliary.<task> block. Auxiliary then falls through to provider auto-detection and resolves OPENROUTER_API_KEY from env. After this change auxiliary.title_generation looks like:

title_generation:
  provider: auto
  model: minimax/minimax-m2.7
  timeout: 30
  extra_body: {}

Verified working — gateway log now shows Auxiliary title_generation: using auto (...) and 401s stop.

PR fix notes

PR #16944: fix(auxiliary): avoid locking into custom path when api_key is empty

Description (problem / solution / changelog)

Problem

When auxiliary.<task> config has base_url set but api_key is empty (common when user expects env var fallback), _resolve_task_provider_model() returns provider="custom" with api_key=None. This causes downstream client construction to make API calls without an Authorization header, resulting in HTTP 401 errors.

This affects any user whose config has the default-looking shape from hermes setup:

auxiliary:
  title_generation:
    provider: openrouter
    model: minimax/minimax-m2.7
    base_url: https://openrouter.ai/api/v1
    api_key: ''    # empty — user expects OPENROUTER_API_KEY fallback

Root Cause

In _resolve_task_provider_model() (agent/auxiliary_client.py:2743):

if cfg_base_url:
    return "custom", resolved_model, cfg_base_url, cfg_api_key, resolved_api_mode

The if cfg_base_url: branch fires before the provider is considered. With cfg_api_key=None (empty string in YAML), the returned tuple drives _get_cached_client("custom", ..., api_key=None), which builds an OpenAI() client with no auth.

Fix

Only return the "custom" provider path when both cfg_base_url and cfg_api_key are non-empty. When base_url is set without api_key but with a known provider (e.g. "openrouter"), pass through to that provider so it can resolve credentials from environment variables.

Before

auxiliary.title_generation: base_url=https://openrouter.ai/api/v1, provider=openrouter, api_key=''
→ Returns ("custom", model, base_url, None, ...) → HTTP 401

After

auxiliary.title_generation: base_url=https://openrouter.ai/api/v1, provider=openrouter, api_key=''
→ Returns ("openrouter", model, base_url, None, ...) → resolves OPENROUTER_API_KEY from env → succeeds

Fixes #16829

Changed files

  • agent/auxiliary_client.py (modified, +7/-1)

PR #16950: fix(auxiliary_client): require api_key for custom provider path

Description (problem / solution / changelog)

Problem

When auxiliary.{task} in config.yaml had provider: auto (or any value) plus a base_url set but api_key empty, _resolve_task_provider_model() fell into the custom provider branch and returned api_key=None. The downstream API call then lacked an Authorization header, causing a 401.

Root Cause

if cfg_base_url:
    return "custom", resolved_model, cfg_base_url, cfg_api_key, resolved_api_mode

The condition only checked base_url, not api_key.

Solution

Change the condition to if cfg_base_url and cfg_api_key: so that a missing/empty api_key falls through to provider auto-detection (or the explicit provider config) instead of locking into custom with no credentials.

Files Changed

  • agent/auxiliary_client.py — 1-line fix
  • tests/agent/test_auxiliary_client.py — 4 regression tests

Fixes #16829

Changed files

  • .mailmap (modified, +3/-0)
  • agent/auxiliary_client.py (modified, +1/-1)
  • scripts/release.py (modified, +2/-1)
  • tests/agent/test_auxiliary_client.py (modified, +63/-0)

Code Example

auxiliary:
  title_generation:
    provider: openrouter        # or auto
    model: minimax/minimax-m2.7
    base_url: https://openrouter.ai/api/v1
    api_key: ''                 # empty — user expects fallback to OPENROUTER_API_KEY env var

---

if task:
    # Config.yaml is the primary source for per-task overrides.
    if cfg_base_url:
        return "custom", resolved_model, cfg_base_url, cfg_api_key, resolved_api_mode
    if cfg_provider and cfg_provider != "auto":
        return cfg_provider, resolved_model, None, None, resolved_api_mode

    return "auto", resolved_model, None, None, resolved_api_mode

---

title_generation:
  provider: auto
  model: minimax/minimax-m2.7
  timeout: 30
  extra_body: {}

---

if cfg_base_url and cfg_api_key:
    return "custom", resolved_model, cfg_base_url, cfg_api_key, resolved_api_mode
RAW_BUFFERClick to expand / collapse

Summary

When an auxiliary.<task> block in ~/.hermes/config.yaml has provider: auto (or any value) plus a base_url set, _resolve_task_provider_model() returns provider="custom" with api_key=None. Downstream client construction then makes the API call without an Authorization header, causing the upstream provider (e.g. OpenRouter) to return 401 Missing Authentication header.

This affects any user whose config has the default-looking shape:

auxiliary:
  title_generation:
    provider: openrouter        # or auto
    model: minimax/minimax-m2.7
    base_url: https://openrouter.ai/api/v1
    api_key: ''                 # empty — user expects fallback to OPENROUTER_API_KEY env var

Source

agent/auxiliary_client.py:2680-2686 (line numbers from local checkout):

if task:
    # Config.yaml is the primary source for per-task overrides.
    if cfg_base_url:
        return "custom", resolved_model, cfg_base_url, cfg_api_key, resolved_api_mode
    if cfg_provider and cfg_provider != "auto":
        return cfg_provider, resolved_model, None, None, resolved_api_mode

    return "auto", resolved_model, None, None, resolved_api_mode

The if cfg_base_url: branch fires before the provider is considered. With cfg_api_key=None (empty string in YAML), the returned tuple drives _get_cached_client("custom", ..., api_key=None), which builds an OpenAI() client with no auth.

Repro

  1. Set OPENROUTER_API_KEY in env (verified: visible to gateway process via ps eww).
  2. In config.yaml, leave auxiliary.title_generation with base_url: https://openrouter.ai/api/v1 and api_key: '' (the values shipped from hermes setup).
  3. Start any new session.
  4. Observe ⚠ banner: Auxiliary title generation failed: HTTP 401: Missing Authentication header.
  5. Gateway log shows: Auxiliary title_generation: using custom (...) — note "custom", not "openrouter".

Expected

When cfg_provider in ("auto", "openrouter", ...) AND cfg_api_key is empty, _resolve_task_provider_model should NOT lock into "custom" mode. Instead it should fall through to provider auto-detection (_resolve_api_key_provider_try_openrouteros.getenv("OPENROUTER_API_KEY")), the same chain the main model uses successfully.

Workaround

Remove both api_key: and base_url: from every auxiliary.<task> block. Auxiliary then falls through to provider auto-detection and resolves OPENROUTER_API_KEY from env. After this change auxiliary.title_generation looks like:

title_generation:
  provider: auto
  model: minimax/minimax-m2.7
  timeout: 30
  extra_body: {}

Verified working — gateway log now shows Auxiliary title_generation: using auto (...) and 401s stop.

Suggested fix

In _resolve_task_provider_model, only return the custom tuple when cfg_api_key is also non-empty:

if cfg_base_url and cfg_api_key:
    return "custom", resolved_model, cfg_base_url, cfg_api_key, resolved_api_mode

Or check whether cfg_base_url matches the main provider's URL and inherit credentials in that case.

Environment

  • Hermes Agent: installed at /Users/heinrichclawdster/.local/bin/hermes (current main as of 2026-04-28)
  • Provider: openrouter for both main and auxiliary
  • macOS, Python 3.11

extent analysis

TL;DR

The most likely fix is to modify the _resolve_task_provider_model function to only return the custom tuple when both cfg_base_url and cfg_api_key are non-empty.

Guidance

  • Verify that the OPENROUTER_API_KEY environment variable is set and visible to the gateway process.
  • Check the auxiliary.<task> block in ~/.hermes/config.yaml for any base_url or api_key settings that may be causing the issue.
  • Consider removing api_key and base_url from the auxiliary.<task> block to fall back to provider auto-detection.
  • Review the _resolve_task_provider_model function to ensure it correctly handles cases where cfg_api_key is empty.

Example

if cfg_base_url and cfg_api_key:
    return "custom", resolved_model, cfg_base_url, cfg_api_key, resolved_api_mode

This code change ensures that the custom tuple is only returned when both cfg_base_url and cfg_api_key are non-empty.

Notes

The suggested fix assumes that the OPENROUTER_API_KEY environment variable is set and visible to the gateway process. If this is not the case, additional configuration may be required.

Recommendation

Apply the suggested fix to the _resolve_task_provider_model function to correctly handle cases where cfg_api_key is empty. This change should resolve the issue and allow the auxiliary title generation to work as expected.

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 auxiliary.{task} with provider:auto + base_url set falls into 'custom' path with empty api_key, returns 401 [2 pull requests, 4 comments, 4 participants]