hermes - ✅(Solved) Fix [bug] delegate_task: api_key falls back to OPENAI_API_KEY instead of provider credential chain when delegation.base_url is set [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#15810Fetched 2026-04-26 05:24:54
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Participants
Timeline (top)
labeled ×4cross-referenced ×2

Discovered while setting up MiniMax M2.7 as the delegation model on v0.10.0. Three compounding bugs were found and patched locally. Two have since been resolved in v0.11.0 (auth header conflict in anthropic_adapter.py, and api_mode detection for MiniMax URLs). This remaining issue persists in v0.11.0.

Happy to open a PR with the fix if useful.

Error Message

if configured_api_key: api_key = configured_api_key elif configured_provider: try: from hermes_cli.runtime_provider import resolve_runtime_provider runtime = resolve_runtime_provider(requested=configured_provider) api_key = runtime.get("api_key", "") or os.getenv("OPENAI_API_KEY", "").strip() except Exception: api_key = os.getenv("OPENAI_API_KEY", "").strip() else: api_key = os.getenv("OPENAI_API_KEY", "").strip()

Root Cause

When delegation.base_url is configured, the base_url branch resolves api_key as:

api_key = configured_api_key or os.getenv("OPENAI_API_KEY", "").strip()

This bypasses the provider credential pool entirely. If OPENAI_API_KEY is unset and the provider key lives in its own env var (e.g. MINIMAX_API_KEY, DASHSCOPE_API_KEY), the child agent receives an empty API key → 401 Unauthorized.

By contrast, the provider-only path (lines 2240+) correctly calls resolve_runtime_provider() which consults the credential pool — so users who omit base_url get correct key resolution. The inconsistency only affects users who set delegation.base_url.


Fix Action

Workaround

Explicitly set delegation.api_key in config.yaml to the provider's API key value. This is not ideal as it duplicates credentials.


PR fix notes

PR #15815: fix(tools): resolve delegation API key from provider credential chain when base_url is set

Description (problem / solution / changelog)

Summary

When delegation.base_url and delegation.provider are both configured, but delegation.api_key is not explicitly set, resolve the API key from the provider credential chain via resolve_runtime_provider() instead of falling back directly to OPENAI_API_KEY.

Root Cause

The base_url branch in _resolve_delegation_credentials() bypassed the provider credential pool entirely and fell back to OPENAI_API_KEY, ignoring provider-specific environment variables like MINIMAX_API_KEY, OPENROUTER_API_KEY, etc. By contrast, the provider-only path correctly calls resolve_runtime_provider() which consults the credential chain.

The inconsistency only affected users who set both delegation.base_url and delegation.provider but did not set delegation.api_key explicitly.

Fix

Added logic in the base_url branch to check if configured_provider is set when configured_api_key is not. In that case:

  1. Call resolve_runtime_provider(requested=configured_provider) to get the runtime configuration
  2. Extract the api_key from the resolved runtime
  3. Preserve the provider name from the credential chain

If provider resolution fails (e.g., provider not configured), fall back to OPENAI_API_KEY with provider set to "custom".

The fix preserves existing behavior when api_key is explicitly set (override mode) but adds credential chain resolution when provider is configured without an explicit api_key.

Regression Coverage

Added regression test test_base_url_with_provider_uses_provider_credential_chain that verifies:

  • When both base_url and provider are set (no explicit api_key)
  • API key is resolved from provider credential chain, not OPENAI_API_KEY
  • Provider name is preserved from configuration
  • resolve_runtime_provider() is called with the correct provider name

Testing

  • All existing TestDelegationCredentialResolution tests pass (11/11)
  • All tests/tools/test_delegate.py tests pass excluding 1 pre-existing failure unrelated to this change (115/115)

Closes #15810

Changed files

  • gateway/platforms/whatsapp.py (modified, +4/-1)
  • tests/tools/test_delegate.py (modified, +39/-0)
  • tests/tools/test_file_tools.py (modified, +37/-0)
  • tools/delegate_tool.py (modified, +22/-2)
  • tools/file_tools.py (modified, +6/-6)

PR #15853: fix(delegate): inherit parent api_key when delegation.base_url set without delegation.api_key (#15810)

Description (problem / solution / changelog)

Summary

  • When delegation.base_url is configured without delegation.api_key, the child agent now inherits the parent's resolved API key instead of falling back to OPENAI_API_KEY
  • Explicit delegation.api_key still overrides as before
  • Two tests updated to reflect the corrected behaviour

The bug

_resolve_delegation_credentials() handled the delegation.base_url path as:

api_key = configured_api_key or os.getenv("OPENAI_API_KEY", "").strip()
if not api_key:
    raise ValueError("... Set delegation.api_key or OPENAI_API_KEY.")

This bypassed the provider credential pool entirely. Providers that store their key outside OPENAI_API_KEY (e.g. MINIMAX_API_KEY, DASHSCOPE_API_KEY) therefore caused the child agent to receive an empty API key → 401 Unauthorized. By contrast, the delegation.provider-only path correctly resolved credentials via resolve_runtime_provider().

The fix

When delegation.api_key is absent, return api_key = None instead of the OPENAI_API_KEY fallback. _spawn_child already has the correct inheritance logic:

effective_api_key = override_api_key or parent_api_key

With override_api_key = None, this naturally falls through to parent_api_key, which holds the key the parent agent resolved through the full provider credential chain.

Test plan

  • Before: test_direct_endpoint_returns_none_api_key_when_not_configured and test_direct_endpoint_no_raise_when_only_provider_env_key_present — both FAIL on unpatched code (one gets wrong "env-openai-key", one expects no ValueError but gets one)
  • After: 115/115 tests pass in tests/tools/test_delegate.py
  • Regression guard: reverted production change → both new tests fail; restored → 0 failures
  • Adjacent toolset tests unchanged

Related

  • Fixes #15810

🤖 Generated with Claude Code

Changed files

  • tests/tools/test_delegate.py (modified, +8/-6)
  • tools/delegate_tool.py (modified, +16/-11)

Code Example

api_key = configured_api_key or os.getenv("OPENAI_API_KEY", "").strip()

---

delegation:
  provider: minimax
  base_url: https://api.minimax.io/anthropic
  api_key: ''

---

if configured_api_key:
    api_key = configured_api_key
elif configured_provider:
    try:
        from hermes_cli.runtime_provider import resolve_runtime_provider
        runtime = resolve_runtime_provider(requested=configured_provider)
        api_key = runtime.get("api_key", "") or os.getenv("OPENAI_API_KEY", "").strip()
    except Exception:
        api_key = os.getenv("OPENAI_API_KEY", "").strip()
else:
    api_key = os.getenv("OPENAI_API_KEY", "").strip()
RAW_BUFFERClick to expand / collapse

Bug: _resolve_delegation_credentials() resolves api_key from OPENAI_API_KEY instead of the provider credential chain when delegation.base_url is set

Version: v0.11.0 (2026.4.23) Affected file: tools/delegate_tool.py_resolve_delegation_credentials() Severity: P2 — degraded but workaround exists


Root Cause

When delegation.base_url is configured, the base_url branch resolves api_key as:

api_key = configured_api_key or os.getenv("OPENAI_API_KEY", "").strip()

This bypasses the provider credential pool entirely. If OPENAI_API_KEY is unset and the provider key lives in its own env var (e.g. MINIMAX_API_KEY, DASHSCOPE_API_KEY), the child agent receives an empty API key → 401 Unauthorized.

By contrast, the provider-only path (lines 2240+) correctly calls resolve_runtime_provider() which consults the credential pool — so users who omit base_url get correct key resolution. The inconsistency only affects users who set delegation.base_url.


Steps to Reproduce

  1. Configure MiniMax as provider with MINIMAX_API_KEY set in environment
  2. Set in config.yaml:
delegation:
  provider: minimax
  base_url: https://api.minimax.io/anthropic
  api_key: ''
  1. Call delegate_task(goal="test", context="test", toolsets=['terminal'], role='leaf')
  2. Child agent receives 401 — api_key resolved as empty string from OPENAI_API_KEY

Proposed Fix

In the base_url branch of _resolve_delegation_credentials(), resolve api_key from the provider credential chain when configured_provider is set:

if configured_api_key:
    api_key = configured_api_key
elif configured_provider:
    try:
        from hermes_cli.runtime_provider import resolve_runtime_provider
        runtime = resolve_runtime_provider(requested=configured_provider)
        api_key = runtime.get("api_key", "") or os.getenv("OPENAI_API_KEY", "").strip()
    except Exception:
        api_key = os.getenv("OPENAI_API_KEY", "").strip()
else:
    api_key = os.getenv("OPENAI_API_KEY", "").strip()

This matches the credential resolution behaviour of the provider-only path.


Workaround

Explicitly set delegation.api_key in config.yaml to the provider's API key value. This is not ideal as it duplicates credentials.


Context

Discovered while setting up MiniMax M2.7 as the delegation model on v0.10.0. Three compounding bugs were found and patched locally. Two have since been resolved in v0.11.0 (auth header conflict in anthropic_adapter.py, and api_mode detection for MiniMax URLs). This remaining issue persists in v0.11.0.

Happy to open a PR with the fix if useful.

extent analysis

TL;DR

The proposed fix involves modifying the _resolve_delegation_credentials() function to resolve the api_key from the provider credential chain when delegation.base_url is set.

Guidance

  • Verify that the configured_provider is set and the api_key is being resolved correctly by checking the values of configured_provider and api_key in the _resolve_delegation_credentials() function.
  • Apply the proposed fix by updating the base_url branch of _resolve_delegation_credentials() to use the provider credential chain.
  • As a temporary workaround, explicitly set delegation.api_key in config.yaml to the provider's API key value, although this is not ideal due to credential duplication.
  • Test the fix by reproducing the issue with the modified code and verifying that the api_key is resolved correctly.

Example

if configured_api_key:
    api_key = configured_api_key
elif configured_provider:
    try:
        from hermes_cli.runtime_provider import resolve_runtime_provider
        runtime = resolve_runtime_provider(requested=configured_provider)
        api_key = runtime.get("api_key", "") or os.getenv("OPENAI_API_KEY", "").strip()
    except Exception:
        api_key = os.getenv("OPENAI_API_KEY", "").strip()
else:
    api_key = os.getenv("OPENAI_API_KEY", "").strip()

Notes

The proposed fix assumes that the resolve_runtime_provider() function is correctly implemented and returns the provider's API key. Additionally, the fix only applies to users who set delegation.base_url and have a provider configured.

Recommendation

Apply the proposed fix to modify the _resolve_delegation_credentials() function, as it correctly resolves the api_key from the provider credential chain when delegation.base_url is set.

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] delegate_task: api_key falls back to OPENAI_API_KEY instead of provider credential chain when delegation.base_url is set [2 pull requests, 1 participants]