hermes - ✅(Solved) Fix [Bug]: `api_key_env` alias silently ignored in `fallback_providers` entries — falls through to "no-key-required" placeholder [2 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#25091Fetched 2026-05-14 03:49:01
View on GitHub
Comments
0
Participants
1
Timeline
7
Reactions
0
Author
Participants
Timeline (top)
labeled ×4cross-referenced ×2referenced ×1

The documented api_key_envkey_env snake_case alias is recognized for entries under the top-level providers: section (via _normalize_custom_provider_entry in hermes_cli/config.py), but not for entries under fallback_providers: in the most recent published release tag v2026.5.7. As a result, when a fallback chain is activated (compression, title generation, etc.), entries written using api_key_env resolve with no API key and fall through to the "no-key-required" placeholder defined in agent/auxiliary_client.py, which is then sent as a Bearer token to the remote endpoint.

For remote providers that enforce auth (DeepSeek, Volcengine ARK, Moonshot, etc.) this produces a 401 with a confusingly masked key — e.g. DeepSeek returns Your api key: ****ired is invalid, where ired is simply the last 4 chars of the literal string no-key-required.

The failure is silent: no warning is emitted when the alias is dropped, and the upstream 401 message is the only signal — which is almost impossible to attribute without reading the source.

Root Cause

Root cause (code references, against v2026.5.7)

The alias normalization lives in hermes_cli/config.py:

Fix Action

Fix / Workaround

Update (2026-05-14) — original body claimed main also reproduces. That was wrong; I only diffed agent/auxiliary_client.py between v2026.5.7 and main and missed that run_agent.py had been patched separately. The real state, after re-checking, is:

  • main (current HEAD 4fdfdf6) is already fixed by commit 6ddc48b0 ("fix(fallback): resolve api_key_env in fallback chain entries (carve-out of #22665)", 2026-05-09).
  • But no released tag contains that commit yet — git tag --contains 6ddc48b0 returns only desktop-pr20059-installers. v2026.5.7 (the latest user-facing tag, released 2026-05-07) does not contain it.

So the issue is now scoped to "fix has merged on main but no release tag carries it." Suggested action below has been revised accordingly.

  1. Cut a patch release (v2026.5.8 or v2026.5.7.1) that includes 6ddc48b0. Users pinned to release tags will then pick it up via normal upgrade.
  2. Cherry-pick 6ddc48b0 onto a release/v2026.5.x branch if one exists and re-tag.

Workaround for users on v2026.5.7

Rename api_key_env:key_env: in every entry under fallback_providers:.

PR fix notes

PR #25063: fix: yuanbao API wiring, silent except logging, api_key_env in fallback_providers

Description (problem / solution / changelog)

Summary

Four focused fixes across the codebase:

1. fix(yuanbao): wire get_chat_info to real group API query (T06)

get_chat_info() returned the raw chat_id string as the chat name for groups. Now calls query_group_info_raw() to fetch real group_name, member_count, owner_id, and owner_nickname. Falls back to chat_id defaults on failure with proper warning logs.

2. fix(api_server): add debug logging to 10 silent except Exception blocks

Replace silent except Exception: pass with logger.debug() + exc_info in non-critical paths in gateway/platforms/api_server.py.

3. fix(auxiliary_client): add debug logging to 5 silent except Exception blocks

Replace silent except Exception: pass with logger.debug() + exc_info in configuration resolution paths in agent/auxiliary_client.py.

4. fix(runtime_provider): resolve api_key_env alias in fallback_providers + providers dict (#25091)

The documented api_key_envkey_env alias was recognized for providers: entries but not for fallback_providers:. Additionally, _get_named_custom_provider() never searched fallback_providers at all, making those entries invisible to the auxiliary client resolver.

Changes:

  • providers dict path: also checks api_key_env alongside key_env
  • New fallback_providers search path: normalizes api_key_envkey_env and matches by name/provider_key

This caused fallback entries with api_key_env to silently resolve to the "no-key-required" placeholder, producing confusing 401s from auth-enforcing providers (DeepSeek, Volcengine ARK, Moonshot, etc.).

5. chore: add missing SKILL.md stubs to 9 empty optional-skill directories

Added SKILL.md stubs to: blockchain, creative, devops, email, finance, mlops, productivity, research, software-development.

Test plan

  • yuanbao get_chat_info returns real group names
  • api_server and auxiliary_client debug logs visible at DEBUG level
  • fallback_providers with api_key_env resolves API key correctly during compression
  • fallback_providers with key_env still works (no regression)
  • providers dict with api_key_env still works

Changed files

  • .github/workflows/docker-publish.yml (modified, +161/-34)
  • agent/auxiliary_client.py (modified, +5/-5)
  • gateway/platforms/api_server.py (modified, +10/-10)
  • gateway/platforms/yuanbao.py (modified, +25/-3)
  • hermes_cli/completion.py (modified, +1/-1)
  • hermes_cli/profiles.py (modified, +0/-85)
  • hermes_cli/runtime_provider.py (modified, +48/-1)
  • optional-skills/blockchain/SKILL.md (added, +19/-0)
  • optional-skills/creative/SKILL.md (added, +21/-0)
  • optional-skills/devops/SKILL.md (added, +19/-0)
  • optional-skills/email/SKILL.md (added, +17/-0)
  • optional-skills/finance/SKILL.md (added, +21/-0)
  • optional-skills/mlops/SKILL.md (added, +22/-0)
  • optional-skills/productivity/SKILL.md (added, +21/-0)
  • optional-skills/research/SKILL.md (added, +21/-0)
  • optional-skills/software-development/SKILL.md (added, +17/-0)
  • scripts/release.py (modified, +1/-0)
  • tests/hermes_cli/test_completion.py (modified, +48/-0)
  • tests/hermes_cli/test_profiles.py (modified, +0/-28)

PR #25294: fix(auxiliary): resolve api_key_env alias in named custom provider path of resolve_provider_client

Description (problem / solution / changelog)

Summary

In resolve_provider_client(), the named custom provider code path only checked the key_env field when looking for an environment-variable-based API key. The documented api_key_env snake_case alias was silently ignored, causing custom providers configured with api_key_env to fall through to the no-key-required placeholder.

Bugs Fixed

  • #25091: api_key_env alias silently dropped in resolve_provider_client() named custom provider path in agent/auxiliary_client.py
  • Mirrors the same fix already applied to run_agent.py in commit 6ddc48b05
  • Adds logger.warning() when key falls back to placeholder for easier debugging

Testing

Test results (1277 passed, 6 known flaky failures, 3 skipped):

  • tests/agent/test_auxiliary_client.py: 155 passed
  • tests/agent/test_auxiliary_named_custom_providers.py: 29 passed
  • tests/agent/test_auxiliary_main_first.py: 21 passed
  • tests/agent/test_auxiliary_config_bridge.py: 2 passed
  • tests/agent/test_auxiliary_transport_autodetect.py: 32 passed
  • tests/run_agent/test_fallback_model.py: 31 passed
  • 6 failures all in tests/hermes_cli/test_api_key_providers.py (pre-existing flaky, unrelated)

Files Changed

FileChange
agent/auxiliary_client.pyAdd api_key_env alias check at line 2913; add logger.warning() when key falls back to no-key-required

Closes #25091

Changed files

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

Code Example

fallback_providers:
     - provider: custom
       model: kimi-k2.6
       base_url: https://ark.cn-beijing.volces.com/api/coding/v3
       api_key_env: ARK_API_KEY          # documented alias — silently dropped here

---

# hermes_cli/config.py: _normalize_custom_provider_entry
"apiKeyEnv": "key_env",  # alias — OpenClaw-compatible + docs variant
...
if "api_key_env" in entry and "key_env" not in entry:
    entry["key_env"] = entry["api_key_env"]

---

fb_api_key_hint = (fb.get("api_key") or "").strip() or None
if not fb_api_key_hint:
    fb_key_env = (fb.get("key_env") or "").strip()    # api_key_env not checked
    if fb_key_env:
        fb_api_key_hint = os.getenv(fb_key_env, "").strip() or None

---

# Local servers (Ollama, llama.cpp, vLLM, LM Studio) don't require auth.
# Use a placeholder key — the OpenAI SDK requires a non-empty string but
# local servers ignore the Authorization header.
if not isinstance(custom_key, str) or not custom_key.strip():
    custom_key = "no-key-required"

---

# key_env and api_key_env are both documented aliases (see
# _normalize_custom_provider_entry in hermes_cli/config.py).
fb_key_env = (fb.get("key_env") or fb.get("api_key_env") or "").strip()
RAW_BUFFERClick to expand / collapse

Update (2026-05-14) — original body claimed main also reproduces. That was wrong; I only diffed agent/auxiliary_client.py between v2026.5.7 and main and missed that run_agent.py had been patched separately. The real state, after re-checking, is:

  • main (current HEAD 4fdfdf6) is already fixed by commit 6ddc48b0 ("fix(fallback): resolve api_key_env in fallback chain entries (carve-out of #22665)", 2026-05-09).
  • But no released tag contains that commit yet — git tag --contains 6ddc48b0 returns only desktop-pr20059-installers. v2026.5.7 (the latest user-facing tag, released 2026-05-07) does not contain it.

So the issue is now scoped to "fix has merged on main but no release tag carries it." Suggested action below has been revised accordingly.

Summary

The documented api_key_envkey_env snake_case alias is recognized for entries under the top-level providers: section (via _normalize_custom_provider_entry in hermes_cli/config.py), but not for entries under fallback_providers: in the most recent published release tag v2026.5.7. As a result, when a fallback chain is activated (compression, title generation, etc.), entries written using api_key_env resolve with no API key and fall through to the "no-key-required" placeholder defined in agent/auxiliary_client.py, which is then sent as a Bearer token to the remote endpoint.

For remote providers that enforce auth (DeepSeek, Volcengine ARK, Moonshot, etc.) this produces a 401 with a confusingly masked key — e.g. DeepSeek returns Your api key: ****ired is invalid, where ired is simply the last 4 chars of the literal string no-key-required.

The failure is silent: no warning is emitted when the alias is dropped, and the upstream 401 message is the only signal — which is almost impossible to attribute without reading the source.

Versions

RefStatus
v2026.5.7 (commit e19fc91, latest release tag)Reproduces
All published release tags prior to v2026.5.7 containing the fallback chain codeReproduce (same run_agent.py path, no alias on either site)
main (commit 4fdfdf6 at time of writing)Fixed by commit 6ddc48b0 (2026-05-09), but no release tag contains it yet

Reproduction (on v2026.5.7)

  1. config.yaml:
    fallback_providers:
      - provider: custom
        model: kimi-k2.6
        base_url: https://ark.cn-beijing.volces.com/api/coding/v3
        api_key_env: ARK_API_KEY          # documented alias — silently dropped here
  2. Have a session long enough to trigger compression (or otherwise force the fallback chain).
  3. agent.log shows the chain activating the kimi fallback, the auxiliary client resolving via agent/auxiliary_client.py, and a 401 from https://ark.cn-beijing.volces.com/... with "API key format is incorrect". ARK_API_KEY is correctly set in .env.

Same reproduces for DeepSeek (api_key_env: DEEPSEEK_API_KEY) — the DeepSeek 401 echoes back the literal no-key-required masked as ****ired.

Expected

The api_key_env alias should resolve identically wherever it appears in config — under providers: and fallback_providers: both.

Actual (on v2026.5.7)

The alias is recognized in providers: but dropped in fallback_providers:, leading to silent auth failure downstream.

Root cause (code references, against v2026.5.7)

The alias normalization lives in hermes_cli/config.py:

# hermes_cli/config.py: _normalize_custom_provider_entry
"apiKeyEnv": "key_env",  # alias — OpenClaw-compatible + docs variant
...
if "api_key_env" in entry and "key_env" not in entry:
    entry["key_env"] = entry["api_key_env"]

But _normalize_custom_provider_entry is only invoked for:

  • providers_dict_to_custom_providers(config.get("providers"))providers: section
  • _append_if_new(_normalize_custom_provider_entry(entry)) inside get_compatible_custom_providers — legacy custom_providers: list

The fallback_providers: consumer in run_agent.py (on v2026.5.7 only) reads the raw dict directly (≈ L7866):

fb_api_key_hint = (fb.get("api_key") or "").strip() or None
if not fb_api_key_hint:
    fb_key_env = (fb.get("key_env") or "").strip()    # api_key_env not checked
    if fb_key_env:
        fb_api_key_hint = os.getenv(fb_key_env, "").strip() or None

When fb_api_key_hint ends up None, the auxiliary client falls through to (agent/auxiliary_client.py ≈ L1519):

# Local servers (Ollama, llama.cpp, vLLM, LM Studio) don't require auth.
# Use a placeholder key — the OpenAI SDK requires a non-empty string but
# local servers ignore the Authorization header.
if not isinstance(custom_key, str) or not custom_key.strip():
    custom_key = "no-key-required"

That placeholder was added for local no-auth servers, but the same code path is reached for remote endpoints whose key resolution silently failed.

Status on main

Commit 6ddc48b0 on 2026-05-09 already applies the alias fix at both consumption sites in run_agent.py. The current main reads, with an explanatory comment:

# key_env and api_key_env are both documented aliases (see
# _normalize_custom_provider_entry in hermes_cli/config.py).
fb_key_env = (fb.get("key_env") or fb.get("api_key_env") or "").strip()

Suggested action

Since main is already fixed, this is a release-engineering rather than code change. Two options for maintainers:

  1. Cut a patch release (v2026.5.8 or v2026.5.7.1) that includes 6ddc48b0. Users pinned to release tags will then pick it up via normal upgrade.
  2. Cherry-pick 6ddc48b0 onto a release/v2026.5.x branch if one exists and re-tag.

In addition, to make the failure mode less hostile to debug if a similar slip happens in the future, please also consider:

  • Emitting a WARNING when a fallback_providers entry ends up with neither api_key nor a resolvable key_env/api_key_env. Currently the only signal is the upstream provider's 401 message with ****ired — which is almost impossible to attribute without reading the source. Even a single log line like "fallback entry '<model>' has no resolvable api_key — request will be sent with placeholder 'no-key-required' and will 401 on auth-required endpoints" would have saved hours of forensics on the user side.
  • (Optional) Pipe fallback_providers entries through _normalize_custom_provider_entry at config load time, the way providers: entries already are, so camelCase aliases (apiKeyEnv, keyEnv) and any future aliases work uniformly without duplicating the alias list at each consumption site.

Workaround for users on v2026.5.7

Rename api_key_env:key_env: in every entry under fallback_providers:.

Related — distinct root causes, same "no-key-required" symptom

  • #9318 — per-task auxiliary override (e.g. auxiliary.session_search) with empty api_key falling through. That path is fixable by inheriting model.api_key. Does not address fallback_providers because users intentionally specify different keys per fallback entry (e.g. ARK_API_KEY for kimi, DEEPSEEK_API_KEY for deepseek), which can't come from a single model.api_key.
  • #16308 — fixes _resolve_task_provider_model() to consult PROVIDER_REGISTRY credentials when base_url overrides a named provider. Does not touch fallback_providers consumption in run_agent.py.
  • #22665 — original PR; 6ddc48b0 is its carve-out for the alias fix.
  • "no-key-required" placeholder introduced in PR #3842 (commit 3cc50532d, 2026-03-29). Intent was correct for local no-auth servers; this issue is about the placeholder being reachable from remote-endpoint code paths via the alias gap.
  • Alias support added in commit c449cd1af (2026-04-13, "fix(config): restore custom providers after v11→v12 migration") — added the alias for providers: but did not extend it to fallback_providers: at the time, which is what 6ddc48b0 belatedly closed.

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]: `api_key_env` alias silently ignored in `fallback_providers` entries — falls through to "no-key-required" placeholder [2 pull requests, 1 participants]