From 78e122ae1ab4e19c5f2bf962c8633434c69af877 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:29:50 -0700 Subject: [PATCH] feat(cron): warn when gateway not running on cron create/list (#51696) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cron ticker only runs inside the gateway (_start_cron_ticker); there is no standalone cron daemon. When the gateway isn't running, next_run_at passes but jobs never fire and last_run_at stays null — and manual 'hermes cron run' (which bypasses the ticker) appears to work, masking the real cause. This is the most common cron support report (#51038). cron list already warned; extend the same warning to cron create (the moment the user is most likely to hit this) via a shared helper, and add a pointer to 'hermes cron status'. Silent when a gateway is running, so the gateway /cron path is unaffected. --- hermes_cli/cron.py | 32 ++++++++++++++++---- tests/hermes_cli/test_cron.py | 57 +++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/hermes_cli/cron.py b/hermes_cli/cron.py index 3c3116970a7..0f3b5a272b3 100644 --- a/hermes_cli/cron.py +++ b/hermes_cli/cron.py @@ -57,6 +57,30 @@ def _cron_api(**kwargs): return json.loads(cronjob_tool(**kwargs)) +def _warn_if_gateway_not_running() -> None: + """Warn that scheduled jobs won't fire unless the gateway is running. + + The cron ticker only runs inside the gateway (``_start_cron_ticker`` in + gateway/run.py); there is no standalone cron daemon. Without a running + gateway, ``next_run_at`` passes but jobs never fire and ``last_run_at`` + stays null — the most common cron support report (#51038). Surfacing this + at create/list time, when the user is right there, prevents it. + """ + try: + from hermes_cli.gateway import find_gateway_pids + + if find_gateway_pids(): + return + except Exception: + # If we can't determine gateway state, stay quiet rather than nag. + return + + print(color(" ⚠ Gateway is not running — jobs won't fire automatically.", Colors.YELLOW)) + print(color(" Start it with: hermes gateway install", Colors.DIM)) + print(color(" sudo hermes gateway install --system # Linux servers", Colors.DIM)) + print(color(" Check status: hermes cron status", Colors.DIM)) + + def cron_list(show_all: bool = False): """List all scheduled jobs.""" from cron.jobs import list_jobs @@ -137,12 +161,7 @@ def cron_list(show_all: bool = False): print() - from hermes_cli.gateway import find_gateway_pids - if not find_gateway_pids(): - print(color(" ⚠ Gateway is not running — jobs won't fire automatically.", Colors.YELLOW)) - print(color(" Start it with: hermes gateway install", Colors.DIM)) - print(color(" sudo hermes gateway install --system # Linux servers", Colors.DIM)) - print() + _warn_if_gateway_not_running() def cron_tick(): @@ -276,6 +295,7 @@ def cron_create(args): if job_data.get("workdir"): print(f" Workdir: {job_data['workdir']}") print(f" Next run: {result['next_run_at']}") + _warn_if_gateway_not_running() return 0 diff --git a/tests/hermes_cli/test_cron.py b/tests/hermes_cli/test_cron.py index 442433f768f..1281589048b 100644 --- a/tests/hermes_cli/test_cron.py +++ b/tests/hermes_cli/test_cron.py @@ -121,3 +121,60 @@ class TestCronCommandLifecycle: out = capsys.readouterr().out assert "Repeat: ∞" in out + + +class TestGatewayNotRunningWarning: + """`cron create` / `cron list` must warn when the gateway (and thus the + cron ticker) isn't running, since jobs only fire inside the gateway. + Regression guard for #51038 — the most common cron 'jobs never fired' + report was simply a gateway that was never started. + """ + + def test_create_warns_when_gateway_absent(self, tmp_cron_dir, capsys, monkeypatch): + monkeypatch.setattr("hermes_cli.gateway.find_gateway_pids", lambda: []) + cron_command( + Namespace( + cron_command="create", + schedule="0 11 * * *", + prompt="Daily report", + name="Daily 1130", + deliver=None, + repeat=None, + skill=None, + skills=None, + script=None, + workdir=None, + no_agent=False, + ) + ) + out = capsys.readouterr().out + assert "Created job" in out + assert "Gateway is not running" in out + + def test_create_silent_when_gateway_running(self, tmp_cron_dir, capsys, monkeypatch): + monkeypatch.setattr("hermes_cli.gateway.find_gateway_pids", lambda: [4242]) + cron_command( + Namespace( + cron_command="create", + schedule="0 11 * * *", + prompt="Daily report", + name="Daily 1130", + deliver=None, + repeat=None, + skill=None, + skills=None, + script=None, + workdir=None, + no_agent=False, + ) + ) + out = capsys.readouterr().out + assert "Created job" in out + assert "Gateway is not running" not in out + + def test_list_warns_when_gateway_absent(self, tmp_cron_dir, capsys, monkeypatch): + create_job(prompt="Daily report", schedule="0 11 * * *") + monkeypatch.setattr("hermes_cli.gateway.find_gateway_pids", lambda: []) + cron_command(Namespace(cron_command="list", all=True)) + out = capsys.readouterr().out + assert "Gateway is not running" in out