Skip to content

fix(auth): surface pool base_url and log errors in _resolve_api_key_p… - #42386

Open
D7y1 wants to merge 1 commit into
NousResearch:mainfrom
D7y1:fix/deepseek-auth-pool-credential-resolution
Open

fix(auth): surface pool base_url and log errors in _resolve_api_key_p…#42386
D7y1 wants to merge 1 commit into
NousResearch:mainfrom
D7y1:fix/deepseek-auth-pool-credential-resolution

Conversation

@D7y1

@D7y1 D7y1 commented Jun 8, 2026

Copy link
Copy Markdown

What does this PR do?

hermes auth add deepseek --type api-key correctly stores the credential to
~/.hermes/auth.jsoncredential_pool.deepseek, but at runtime
resolve_api_key_provider_credentials() silently returns an empty api_key,
causing every inference request to fail with:

Provider 'deepseek' is set in config.yaml but no API key was found.
Set the DEEPSEEK_API_KEY environment variable, or switch to a different provider with `hermes model`.

Two root causes fixed:

1. Silent exception swallowing_resolve_api_key_provider_secret() wrapped
the entire credential pool lookup in except Exception: pass. Any failure in
load_pool() (import error, I/O error, lock contention) was invisible: the
function fell through to return "", "" as if no credentials existed, with no
log entry to debug from.

2. pool base_url silently dropped — when the key was successfully read
from the pool, the pool entry's base_url (written by hermes auth add) was
discarded. resolve_api_key_provider_credentials() always fell back to
pconfig.inference_base_url, ignoring the URL stored alongside the key. This
matters for providers added against a non-default endpoint (e.g. a
DeepSeek-compatible server at a custom URL).

Related Issue

Fixes #42269

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✅ Tests (adding or improving test coverage)

Changes Made

hermes_cli/auth.py — two targeted edits, no interface changes (+33 / -1):

  • _resolve_api_key_provider_secret():
    Replace except Exception: pass with logger.debug(..., exc_info=True) so
    load_pool() failures are visible in hermes --debug output instead of
    silently appearing as "no credentials found".

  • resolve_api_key_provider_credentials():
    When key_source indicates the key came from the credential pool, do a
    focused lookup for the pool entry's runtime_base_url and prefer it over
    pconfig.inference_base_url. The _resolve_api_key_provider_secret return
    type is unchanged — the base_url concern stays in the one function
    that assembles the full credential dict, not propagated to all callers.

tests/hermes_cli/test_deepseek_auth_pool_resolution.py (new):
5 integration tests that write a real auth.json pool entry (exactly as
hermes auth add deepseek does) and call resolve_api_key_provider_credentials()
end-to-end without mocking load_pool.

How to Test

Automated (recommended):

pytest tests/hermes_cli/test_deepseek_auth_pool_resolution.py -v

All 5 tests should pass. test_resolve_api_key_credentials_finds_pool_key
directly reproduces the bug: it seeds a pool entry exactly as hermes auth add
would, then calls resolve_api_key_provider_credentials("deepseek") and asserts
the key is returned — this test fails on main and passes with this fix.

Manual:

  1. Make sure DEEPSEEK_API_KEY is not set in your environment
  2. hermes auth add deepseek --type api-key → paste a real or fake key
  3. hermes model → select DeepSeek
  4. hermes chat -q "hello" → should send the request (previously raised: Provider 'deepseek' is set in config.yaml but no API key was found)
  5. hermes --debug chat -q "hello" → if the key is invalid you will see a 401 from DeepSeek instead of the "no API key" error, confirming the pool is being read

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Windows 11

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

Before fix — pool credential ignored, error raised:

Provider 'deepseek' is set in config.yaml but no API key was found.
Set the DEEPSEEK_API_KEY environment variable, or switch to a different provider with `hermes model`.

After fix — integration test output confirming end-to-end resolution works:

tests/hermes_cli/test_deepseek_auth_pool_resolution.py::test_resolve_api_key_credentials_finds_pool_key PASSED
tests/hermes_cli/test_deepseek_auth_pool_resolution.py::test_resolve_finds_pool_key_over_empty_env     PASSED
tests/hermes_cli/test_deepseek_auth_pool_resolution.py::test_env_var_takes_priority_over_pool          PASSED
tests/hermes_cli/test_deepseek_auth_pool_resolution.py::test_empty_pool_returns_empty_key              PASSED
tests/hermes_cli/test_deepseek_auth_pool_resolution.py::test_pool_base_url_propagated_to_credentials   PASSED
5 passed in 1.11s

…rovider_secret

When a provider credential is stored via `hermes auth add` (e.g. DeepSeek),
the runtime resolver found the key in the credential pool but silently
discarded the pool entry's base_url and swallowed all exceptions with a
bare `except Exception: pass`.

Two bugs fixed in hermes_cli/auth.py:

1. `_resolve_api_key_provider_secret`: replace `except Exception: pass`
   with `logger.debug(..., exc_info=True)` so load_pool() failures are
   visible in debug logs instead of silently looking like no credentials.

2. `resolve_api_key_provider_credentials`: when the key came from the
   credential pool, do a focused lookup for the pool entry's runtime_base_url
   and prefer it over pconfig.inference_base_url. Fixes providers added
   against a non-default endpoint. The `_resolve_api_key_provider_secret`
   return type is unchanged -- the base_url concern stays in the one
   function that assembles the full credential dict, not scattered across
   all callers.

Integration tests in test_deepseek_auth_pool_resolution.py write a real
auth.json pool entry (exactly as `hermes auth add deepseek` does) and
exercise the full resolution chain without mocking load_pool.

Closes NousResearch#42269
@D7y1
D7y1 force-pushed the fix/deepseek-auth-pool-credential-resolution branch from cb69df3 to 54c7d25 Compare June 8, 2026 21:41
@liuhao1024

Copy link
Copy Markdown
Contributor

Positive review — credential pool base_url propagation looks solid.

The fix addresses two real issues: (1) silent exception swallowing in _resolve_api_key_provider_secret that made pool failures invisible, and (2) pool entry's base_url being dropped when resolving credentials, causing callers to fall back to pconfig.inference_base_url even when a custom URL was stored with the key.

Observations:

  • The key_source.startswith("credential_pool:") guard correctly scopes the pool lookup to cases where the key actually came from the pool.
  • Pool lookup failure is handled gracefully — peek() returning None just means no propagation, not a crash.
  • The priority chain (env_url > zai_base_url > pool_base_url > pconfig.inference_base_url) is correct — env vars should still win.
  • Test coverage is thorough: pool key resolution, env var precedence, empty pool, and base_url propagation are all covered with real credential pool writes (not mocked load_pool).

No issues found.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/auth Authentication, OAuth, credential pools provider/deepseek DeepSeek API labels Jun 8, 2026
@DeamonDev888

Copy link
Copy Markdown

This PR surfaces pool base_url and logs errors — #62467 builds on that foundation.

What this PR does: makes entry.base_url visible in error messages and logs during _resolve_api_key_provider_credentials. Good diagnostic improvement.

How #62467 extends this:

  1. hermes auth list shows the base_url — not just in logs, but in the user-facing CLI output. Compact tags (coding, anthropic, minimax-cn) make it readable:
zai (9 credentials, strategy: round_robin):
  #1  GLM 22    api_key manual  coding ←
  #2  GLM 7     api_key manual  coding
  1. REST API exposes base_urlGET /api/credentials/pool/{provider} returns base_url for each entry (but never api_key), enabling the Desktop UI to show it.

  2. Unified resolver returns source labelingResolvedCredential includes whether the URL came from pool, env, config, probe, or registry. This makes diagnostic logs much more actionable: instead of "base_url was wrong", you get "base_url came from stale auth.json cache, env var GLM_BASE_URL would have overridden it".

If this PR merges first, #62467 will rebase cleanly (the unified resolver delegates through the same path). The logging improvements here would complement the resolver's source tracking nicely.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused auth investigation. The reported runtime path has moved since this PR was opened: current main selects the pool entry in hermes_cli/runtime_provider.py:1694-1760 and preserves its endpoint in _resolve_runtime_from_pool_entry() at hermes_cli/runtime_provider.py:415. Both CLI and gateway agent setup use that resolver (hermes_cli/cli_agent_setup_mixin.py:41-45, gateway/run.py:1857-1858).

Problems

  • The new tests exercise resolve_api_key_provider_credentials() rather than the current runtime path, so they do not regress the reported CLI/gateway failure.
  • The proposed propagation reloads the pool after the helper has already selected a key. A rotating or concurrently updated pool can therefore pair one credential's key with another credential's endpoint; see pool selection/current behavior in agent/credential_pool.py:1539-1565.

Suggested changes

  • Re-scope the regression test to resolve_runtime_provider() using a real DeepSeek pool entry with a non-default endpoint.
  • If helper-level propagation remains necessary, return/use the same pool entry for both key and URL instead of performing a second load_pool(...).peek().
  • The debug logging improvement can be retained as a separate, focused diagnostic change.

Automated hermes-sweeper review.

@alt-glitch alt-glitch added needs-repro Bug needs reproduction steps needs-decision Awaiting maintainer decision before any implementation labels Jul 14, 2026
@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 14, 2026
@alt-glitch alt-glitch removed the sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades label Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/cli CLI entry point, hermes_cli/, setup wizard needs-decision Awaiting maintainer decision before any implementation needs-repro Bug needs reproduction steps P2 Medium — degraded but workaround exists provider/deepseek DeepSeek API sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hermes auth add deepseek: credential stored to pool but provider resolution ignores it at runtime

5 participants