Merge pull request #116328 from NousResearch/fix/boa-w3-new-reports-cron-codex

fix(cron): missing-credential preflight verdict names the profile and HERMES_HOME it read (#116213)
This commit is contained in:
Teknium
2026-09-19 14:33:58 -07:00
committed by GitHub
3 changed files with 69 additions and 2 deletions

View File

@@ -111,8 +111,8 @@ def _preflight_check_provider_key(job: dict, cfg: dict) -> Optional[str]:
# the job through the provider's window (cron/quota_hold.py, #89376).
return None
return (
f"provider credential missing: {exc}. "
"Set the provider API key in .env (or `hermes setup`), or pin a "
f"provider credential missing: {exc} {_credential_store_scope_label()}. "
"Set the provider API key in .env (or `hermes setup`) for that home, or pin a "
"working provider via `hermes cron edit "
f"{job.get('id')} --provider <p>`."
)
@@ -121,6 +121,19 @@ def _preflight_check_provider_key(job: dict, cfg: dict) -> Optional[str]:
return None
def _credential_store_scope_label() -> str:
"""``[profile '<name>', HERMES_HOME <path>]`` for the home this preflight read credentials from.
The verdict must name the store it judged: a scheduler process whose home differs from the
shell where "the same credential works" (Docker HOME vs HERMES_HOME, a multiplexed satellite
profile, a gateway launched without the shell's env) otherwise reports a bare "No credentials
stored" that cannot be told apart from a real login gap (#116213).
"""
from hermes_cli.profiles import get_active_profile_name
from hermes_constants import get_hermes_home
return f"[profile '{get_active_profile_name() or 'default'}', HERMES_HOME {get_hermes_home()}]"
def _primary_profile_routes_for_current_home() -> list:
"""Primary gateway ``profile_routes`` targeting the profile being served; ``[]`` if this IS the
primary home. Satellite crons are ticked and delivered by the primary gateway (a satellite

View File

@@ -0,0 +1,46 @@
"""A ``blocked_config`` credential verdict names the profile + HERMES_HOME the scheduler actually
read (#116213): "No Codex credentials stored" from a gateway whose home differs from the shell that
works is otherwise indistinguishable from a genuine login gap."""
import json
import re
import pytest
from cron.scheduler_preflight import _preflight_check_provider_key
from cron.scheduler_provider import _profile_cron_scope
JOB = {"id": "7a6ae427c1d8", "name": "radar", "provider": "openai-codex", "model": "gpt-5.6-sol"}
@pytest.fixture
def two_homes(tmp_path, monkeypatch):
root = tmp_path / "root"
alpha = root / "profiles" / "alpha"
alpha.mkdir(parents=True)
for home in (root, alpha):
(home / "config.yaml").write_text("model:\n default: gpt-5.6-sol\n provider: openai-codex\n")
(root / "auth.json").write_text(json.dumps({"version": 1, "providers": {}}))
monkeypatch.setenv("HERMES_HOME", str(root))
monkeypatch.setenv("HOME", str(tmp_path / "home"))
monkeypatch.delenv("HERMES_PROFILE", raising=False)
return root, alpha
def _scope(reason: str) -> tuple:
match = re.search(r"\[profile '([^']+)', HERMES_HOME (.+?)\]", reason)
assert match, reason
return match.group(1), match.group(2)
def test_missing_codex_credential_verdict_names_the_home_it_read(two_homes):
root, alpha = two_homes
reason = _preflight_check_provider_key(JOB, {"cron": {}})
assert reason and "No Codex credentials stored" in reason
assert _scope(reason) == ("default", str(root))
# Multiplex tick of a satellite profile: the verdict names alpha, not the gateway's launch home.
with _profile_cron_scope(alpha):
reason = _preflight_check_provider_key(JOB, {"cron": {}})
assert reason and _scope(reason) == ("alpha", str(alpha))

View File

@@ -99,6 +99,14 @@ alert is delivered (it is not repeated every tick), and **no LLM call is
made** — a misconfigured job never spends tokens. The next healthy run clears
the blocked state so a future configuration break alerts again.
A missing-credential verdict names the profile and `HERMES_HOME` the scheduler
read, e.g. `provider credential missing: No Codex credentials stored … [profile
'default', HERMES_HOME /opt/data]`. When an interactive session with "the same"
credential works, compare that path with the shell's `HERMES_HOME`: a gateway
started without the shell's environment (Docker `HOME` vs `HERMES_HOME`, a
service unit) or a multiplexed satellite profile reads a different `auth.json`
and `.env` than the shell does.
To disable the validation and restore the old behavior (the run proceeds and
fails during execution):