hermes - 💡(How to fix) Fix Cron scheduler doesn't propagate model.api_key / model.base_url from config.yaml to resolve_runtime_provider()

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…

When a cron job inherits the custom provider from config.yaml (i.e. the job has no explicit model/provider/base_url set), the API call fails with API key required after exhausting all 3 retries. Interactive CLI sessions with the identical config work fine.

Root Cause

The cron scheduler (cron/scheduler.py, ~line 1487) builds the kwargs for resolve_runtime_provider() without reading model.api_key or model.base_url from the loaded config:

runtime_kwargs = {"requested": job.get("provider")}
if job.get("base_url"):
    runtime_kwargs["explicit_base_url"] = job.get("base_url")
# model.api_key and model.base_url from config.yaml are never passed
runtime = resolve_runtime_provider(**runtime_kwargs)

Inside resolve_runtime_provider() (hermes_cli/runtime_provider.py), when requested_provider == "custom":

  1. _resolve_named_custom_runtime() (line 649) has a fast-path for custom with an explicit base_url — but it checks explicit_base_url, not model_cfg.get("base_url"). Since nothing was passed, it falls through.
  2. _get_named_custom_provider("custom") (line 479) returns None immediately — bare "custom" is intentionally excluded from named provider lookup.
  3. _resolve_explicit_runtime() (line 1064) returns None because both explicit_api_key and explicit_base_url are empty.
  4. No credential pool exists for "custom".
  5. None of the named provider branches (nous, anthropic, bedrock, etc.) match.
  6. Falls through to _resolve_openrouter_runtime(), which fails with API key required.

The model_cfg dict (containing base_url, api_key, api_mode) is loaded at line 1268 inside the resolver but is never used to populate the custom provider path when no explicit overrides are passed.

Fix Action

Fix / Workaround

Alternatively (or additionally), _resolve_named_custom_runtime() could be patched to also read from model_cfg when explicit_base_url is not set but model_cfg["base_url"] exists — though this is a broader change that affects all callers.

Code Example

runtime_kwargs = {"requested": job.get("provider")}
if job.get("base_url"):
    runtime_kwargs["explicit_base_url"] = job.get("base_url")
# model.api_key and model.base_url from config.yaml are never passed
runtime = resolve_runtime_provider(**runtime_kwargs)

---

if requested_norm == "custom" and explicit_base_url:
    # ... resolves api_key from candidates including explicit_api_key

---

model:
     default: some-model
     provider: custom
     base_url: https://custom-endpoint.example.com/v1
     api_key: sk-...

---

hermes cron create "test" --schedule "*/5 * * * *"

---

runtime_kwargs = {"requested": job.get("provider")}
if job.get("base_url"):
    runtime_kwargs["explicit_base_url"] = job.get("base_url")
else:
    _model_cfg_for_runtime = _cfg.get("model", {})
    if isinstance(_model_cfg_for_runtime, dict):
        if _model_cfg_for_runtime.get("base_url"):
            runtime_kwargs["explicit_base_url"] = str(
                _model_cfg_for_runtime["base_url"]
            ).strip()
        if _model_cfg_for_runtime.get("api_key"):
            runtime_kwargs["explicit_api_key"] = str(
                _model_cfg_for_runtime["api_key"]
            ).strip()
runtime = resolve_runtime_provider(**runtime_kwargs)
RAW_BUFFERClick to expand / collapse

Description

When a cron job inherits the custom provider from config.yaml (i.e. the job has no explicit model/provider/base_url set), the API call fails with API key required after exhausting all 3 retries. Interactive CLI sessions with the identical config work fine.

Root Cause

The cron scheduler (cron/scheduler.py, ~line 1487) builds the kwargs for resolve_runtime_provider() without reading model.api_key or model.base_url from the loaded config:

runtime_kwargs = {"requested": job.get("provider")}
if job.get("base_url"):
    runtime_kwargs["explicit_base_url"] = job.get("base_url")
# model.api_key and model.base_url from config.yaml are never passed
runtime = resolve_runtime_provider(**runtime_kwargs)

Inside resolve_runtime_provider() (hermes_cli/runtime_provider.py), when requested_provider == "custom":

  1. _resolve_named_custom_runtime() (line 649) has a fast-path for custom with an explicit base_url — but it checks explicit_base_url, not model_cfg.get("base_url"). Since nothing was passed, it falls through.
  2. _get_named_custom_provider("custom") (line 479) returns None immediately — bare "custom" is intentionally excluded from named provider lookup.
  3. _resolve_explicit_runtime() (line 1064) returns None because both explicit_api_key and explicit_base_url are empty.
  4. No credential pool exists for "custom".
  5. None of the named provider branches (nous, anthropic, bedrock, etc.) match.
  6. Falls through to _resolve_openrouter_runtime(), which fails with API key required.

The model_cfg dict (containing base_url, api_key, api_mode) is loaded at line 1268 inside the resolver but is never used to populate the custom provider path when no explicit overrides are passed.

Why Interactive Sessions Are Unaffected

The CLI code path (cli.py) reads model.api_key and model.base_url from config and passes them as explicit_api_key / explicit_base_url to resolve_runtime_provider(). This hits the early return at line 649:

if requested_norm == "custom" and explicit_base_url:
    # ... resolves api_key from candidates including explicit_api_key

The cron scheduler lacks this propagation.

Reproduction

  1. Set config.yaml with a custom provider endpoint:
    model:
      default: some-model
      provider: custom
      base_url: https://custom-endpoint.example.com/v1
      api_key: sk-...
  2. Create a cron job without explicit model/provider/base_url:
    hermes cron create "test" --schedule "*/5 * * * *"
  3. Wait for execution — all 3 retries fail with API key required.
  4. Run the same prompt interactively — works fine.

Expected Behavior

The cron scheduler should read model.base_url and model.api_key from config.yaml and pass them to resolve_runtime_provider(), matching the CLI behavior.

Suggested Fix

In cron/scheduler.py around line 1487, after loading _cfg, propagate the config values when the job does not set them explicitly:

runtime_kwargs = {"requested": job.get("provider")}
if job.get("base_url"):
    runtime_kwargs["explicit_base_url"] = job.get("base_url")
else:
    _model_cfg_for_runtime = _cfg.get("model", {})
    if isinstance(_model_cfg_for_runtime, dict):
        if _model_cfg_for_runtime.get("base_url"):
            runtime_kwargs["explicit_base_url"] = str(
                _model_cfg_for_runtime["base_url"]
            ).strip()
        if _model_cfg_for_runtime.get("api_key"):
            runtime_kwargs["explicit_api_key"] = str(
                _model_cfg_for_runtime["api_key"]
            ).strip()
runtime = resolve_runtime_provider(**runtime_kwargs)

Alternatively (or additionally), _resolve_named_custom_runtime() could be patched to also read from model_cfg when explicit_base_url is not set but model_cfg["base_url"] exists — though this is a broader change that affects all callers.

Environment

  • Hermes Agent version: latest (main branch)
  • Provider: custom with model.base_url + model.api_key in config.yaml
  • Cron job: no explicit model/provider/base_url (inherits from config)

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