mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix: reject stale one-shot cron jobs
This commit is contained in:
parent
845a2d8152
commit
848089ac93
3 changed files with 48 additions and 1 deletions
15
cron/jobs.py
15
cron/jobs.py
|
|
@ -978,6 +978,19 @@ def create_job(
|
|||
no_agent=normalized_no_agent,
|
||||
)
|
||||
|
||||
next_run_at = compute_next_run(parsed_schedule)
|
||||
if parsed_schedule.get("kind") == "once" and next_run_at is None:
|
||||
run_at = parsed_schedule.get("run_at") or schedule
|
||||
logger.warning(
|
||||
"Rejecting one-shot cron job '%s': run_at %s is outside the %ss grace window",
|
||||
name or label_source[:50].strip(),
|
||||
run_at,
|
||||
ONESHOT_GRACE_SECONDS,
|
||||
)
|
||||
raise ValueError(
|
||||
f"Requested one-shot time {run_at} is in the past and cannot be scheduled."
|
||||
)
|
||||
|
||||
job = {
|
||||
"id": job_id,
|
||||
"name": name or label_source[:50].strip(),
|
||||
|
|
@ -1006,7 +1019,7 @@ def create_job(
|
|||
"paused_at": None,
|
||||
"paused_reason": None,
|
||||
"created_at": now,
|
||||
"next_run_at": compute_next_run(parsed_schedule),
|
||||
"next_run_at": next_run_at,
|
||||
"last_run_at": None,
|
||||
"last_status": None,
|
||||
"last_error": None,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import json
|
|||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
|
@ -86,6 +87,19 @@ class TestJobScriptField:
|
|||
assert updated.get("script") is None
|
||||
|
||||
|
||||
def test_cronjob_tool_rejects_stale_past_one_shot(cron_env, monkeypatch):
|
||||
from tools.cronjob_tools import cronjob
|
||||
|
||||
now = datetime(2026, 3, 18, 4, 30, 0, tzinfo=timezone.utc)
|
||||
monkeypatch.setattr("cron.jobs._hermes_now", lambda: now)
|
||||
stale = (now - timedelta(minutes=5)).isoformat()
|
||||
|
||||
result = json.loads(cronjob(action="create", prompt="Too late", schedule=stale))
|
||||
|
||||
assert result["success"] is False
|
||||
assert "past and cannot be scheduled" in result["error"]
|
||||
|
||||
|
||||
class TestRunJobScript:
|
||||
"""Test the _run_job_script() function."""
|
||||
|
||||
|
|
|
|||
|
|
@ -305,6 +305,26 @@ class TestJobCRUD:
|
|||
job = create_job(prompt="One-shot", schedule="1h")
|
||||
assert job["repeat"]["times"] == 1
|
||||
|
||||
def test_rejects_stale_past_one_shot_at_creation(self, tmp_cron_dir, monkeypatch):
|
||||
now = datetime(2026, 3, 18, 4, 30, 0, tzinfo=timezone.utc)
|
||||
monkeypatch.setattr("cron.jobs._hermes_now", lambda: now)
|
||||
stale = (now - timedelta(minutes=5)).isoformat()
|
||||
|
||||
with pytest.raises(ValueError, match="past and cannot be scheduled"):
|
||||
create_job(prompt="Too late", schedule=stale)
|
||||
|
||||
assert load_jobs() == []
|
||||
|
||||
def test_recent_past_one_shot_within_grace_still_creates(self, tmp_cron_dir, monkeypatch):
|
||||
now = datetime(2026, 3, 18, 4, 30, 30, tzinfo=timezone.utc)
|
||||
monkeypatch.setattr("cron.jobs._hermes_now", lambda: now)
|
||||
recent = (now - timedelta(seconds=30)).isoformat()
|
||||
|
||||
job = create_job(prompt="Still valid", schedule=recent)
|
||||
|
||||
assert job["next_run_at"] == recent
|
||||
assert load_jobs()[0]["id"] == job["id"]
|
||||
|
||||
def test_interval_no_auto_repeat(self, tmp_cron_dir):
|
||||
job = create_job(prompt="Recurring", schedule="every 1h")
|
||||
assert job["repeat"]["times"] is None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue