# NousResearch/hermes-agent · Delegation API key credential chain resolution bug

## Open now

- 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.

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #19741. Verify it addresses your case.
- **Recommended:** read PR #19741 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-19741`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: the share of member 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.

## Closed / duplicate issues

- #15810: \[bug\] delegate_task: api_key falls back to OPENAI_API_KEY instead of provider credential chain when delegation.base_url is set

## Prior & competing PRs

- **#15815: fix(tools): resolve delegation API key from provider credential chain when base_url is set**. closed without merge · fix tier `suggested`
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#15810`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#19741: fix(delegate): inherit parent api_key when delegation.base_url set without delegation.api_key**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `tests/tools/test_delegate.py`, `tools/delegate_tool.py`
  - fix hunks:
  - `tests/tools/test_delegate.py`

    ```diff
    @@ -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",
    ```

    ```diff
    @@ -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",
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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.
    ```

    ```diff
    @@ -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"
    ```
- **#15853: fix(delegate): inherit parent api_key when delegation.base_url set without delegation.api_key (#15810)**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#19741`

## Fix status

- Merged to main in PR #19741 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-04`.
