NousResearch/hermes-agent

Delegation API key credential chain resolution bug

Cluster 15810. Data as of

Nothing open on GitHub. Fix merged in #19741 on 4 May 2026. Check it covers your case.

The short answer

0open issues
0open PRs
4items grouped
Known?
Yes. 4 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #19741. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #19741. That is separate from the fix event.
Still open
Nothing open on GitHub.

A bug where delegate_task falls back to OPENAI_API_KEY instead of the correct provider credential chain when delegation.base_url is set without an explicit delegation.api_key. The fixes make delegation inherit the parent API key or resolve from the proper provider credentials.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #19741 fix(delegate): inherit parent api_key when delegation.base_url set without delegation.api_key

    merged to main

    files: tests/tools/test_delegate.py, tools/delegate_tool.py

    Show the diff
    • tests/tools/test_delegate.py
      @@ -821,7 +821,9 @@ def test_direct_endpoint_uses_configured_base_url_and_api_key(self):
               self.assertEqual(creds["api_key"], "local-key")
               self.assertEqual(creds["api_mode"], "chat_completions")
       
      -    def test_direct_endpoint_falls_back_to_openai_api_key_env(self):
      +    def test_direct_endpoint_returns_none_api_key_when_not_configured(self):
      +        # When base_url is set without api_key, api_key should be None so
      +        # _build_child_agent inherits the parent's key (effective_api_key = override or parent).
               parent = _make_mock_parent(depth=0)
               cfg = {
                   "model": "qwen2.5-coder",
      @@ -829,10 +831,11 @@ def test_direct_endpoint_falls_back_to_openai_api_key_env(self):
               }
               with patch.dict(os.environ, {"OPENAI_API_KEY": "[redacted:secret-assignment]"}, clear=False):
                   creds = _resolve_delegation_credentials(cfg, parent)
      -        self.assertEqual(creds["api_key"], "env-openai-key")
      +        self.assertIsNone(creds["api_key"])
               self.assertEqual(creds["provider"], "custom")
       
      -    def test_direct_endpoint_does_not_fall_back_to_openrouter_api_key_env(self):
      +    def test_direct_endpoint_no_raise_when_only_provider_env_key_present(self):
      +        # Even if OPENAI_API_KEY is absent, no ValueError — _build_child_agent uses parent key.
               parent = _make_mock_parent(depth=0)
               cfg = {
                   "model": "qwen2.5-coder",
      @@ -846,9 +849,9 @@ def test_direct_endpoint_does_not_fall_back_to_openrouter_api_key_env(self):
                   },
                   clear=False,
               ):
      -            with self.assertRaises(ValueError) as ctx:
      -                _resolve_delegation_credentials(cfg, parent)
      -        self.assertIn("OPENAI_API_KEY", str(ctx.exception))
      +            creds = _resolve_delegation_credentials(cfg, parent)
      +        self.assertIsNone(creds["api_key"])
      +        self.assertEqual(creds["provider"], "custom")
       
           @patch("hermes_cli.runtime_provider.resolve_runtime_provider")
           def test_nous_provider_resolves_nous_credentials(self, mock_resolve):
    • tools/delegate_tool.py
      @@ -2237,11 +2237,17 @@ def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict:
           """Resolve credentials for subagent delegation.
       
           If ``delegation.base_url`` is configured, subagents use that direct
      -    OpenAI-compatible endpoint. Otherwise, if ``delegation.provider`` is
      -    configured, the full credential bundle (base_url, api_key, api_mode,
      -    provider) is resolved via the runtime provider system — the same path used
      -    by CLI/gateway startup. This lets subagents run on a completely different
      -    provider:model pair.
      +    OpenAI-compatible endpoint. ``delegation.api_key`` overrides the key; when
      +    omitted, ``api_key`` is returned as ``None`` so ``_build_child_agent``
      +    inherits the parent agent's key (``effective_api_key = [redacted:secret-assignment] or
      +    parent_api_key``). This lets providers that store their key outside
      +    ``OPENAI_API_KEY`` (e.g. ``MINIMAX_API_KEY``, ``DASHSCOPE_API_KEY``) work
      +    without a duplicate config entry.
      +
      +    Otherwise, if ``delegation.provider`` is configured, the full credential
      +    bundle (base_url, api_key, api_mode, provider) is resolved via the runtime
      +    provider system — the same path used by CLI/gateway startup. This lets
      +    subagents run on a completely different provider:model pair.
       
           If neither base_url nor provider is configured, returns None values so the
           child inherits everything from the parent agent.
      @@ -2254,12 +2260,13 @@ def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict:
           configured_api_key = [redacted:secret-assignment]"api_key") or "").strip() or None
       
           if configured_base_url:
      -        api_key = [redacted:secret-assignment] or os.getenv("OPENAI_API_KEY", "").strip()
      -        if not api_key:
      -            raise ValueError(
      -                "Delegation base_url is configured but no API key was found. "
      -                "Set delegation.api_key or OPENAI_API_KEY."
      -            )
      +        # When delegation.api_key is not set, return None so _build_child_agent
      +        # falls back to the parent agent's API key via the credential inheritance
      +        # path (effective_api_key = [redacted:secret-assignment] or parent_api_key). This
      +        # lets providers that store their key in a non-OPENAI_API_KEY env var
      +        # (e.g. MINIMAX_API_KEY, DASHSCOPE_API_KEY) work without requiring
      +        # callers to duplicate the key under delegation.api_key.
      +        api_key = [redacted:secret-assignment]  # None → inherited from parent in _build_child_agent
       
               base_lower = configured_base_url.lower()
               provider = "custom"

2 PRs closed without merging

From the close comments: merged via another change 1 · other 1

  • #15815 fix(tools): resolve delegation API key from provider credential chain when base_url is set · other → #15810
  • #15853 fix(delegate): inherit parent api_key when delegation.base_url set without delegation.api_key (#15810) · merged via another change → #19741
Closed issues · 1
  • Closed#15810[bug] delegate_task: api_key falls back to OPENAI_API_KEY instead of provider credential chain when delegation.base_url is set
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.50: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#19741
Closed issues
1
Attempts
2 PRs closed without merging
Fix link checked
4 May 2026

For your agent

The same facts, typed.