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:
@@ -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
|
||||
|
||||
46
tests/cron/test_preflight_credential_verdict_names_home.py
Normal file
46
tests/cron/test_preflight_credential_verdict_names_home.py
Normal 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))
|
||||
@@ -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):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user