NousResearch/hermes-agent

Cron jobs deleted after one run; repeat=-1 normalization

Cluster 2611 · cron. Data as of

Nothing open on GitHub. Fix merged in #2612 on 23 Mar 2026. Check it covers your case.

The short answer

0open issues
0open PRs
3items grouped
Known?
Yes. 3 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #2612. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #2612. That is separate from the fix event.
Still open
Nothing open on GitHub.

Three items concern cron jobs being deleted after a single run, apparently when an LLM passes repeat=-1 to mean 'forever'. Two titles propose normalizing repeat values of -1 or less to None, and the third is a bug report describing the deletion.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #2612 fix: normalize repeat<=0 to None — cron jobs deleted after first run when LLM passes -1

    merged to main

    files: cron/jobs.py, tests/cron/test_jobs.py, tools/cronjob_tools.py

    Show the diff
    • cron/jobs.py
      @@ -383,6 +383,10 @@ def create_job(
           """
           parsed_schedule = parse_schedule(schedule)
       
      +    # Normalize repeat: treat 0 or negative values as None (infinite)
      +    if repeat is not None and repeat <= 0:
      +        repeat = None
      +
           # Auto-set repeat=1 for one-shot schedules if not specified
           if parsed_schedule["kind"] == "once" and repeat is None:
               repeat = 1
      @@ -571,7 +575,7 @@ def mark_job_run(job_id: str, success: bool, error: Optional[str] = None):
                       # Check if we've hit the repeat limit
                       times = job["repeat"].get("times")
                       completed = job["repeat"]["completed"]
      -                if times is not None and completed >= times:
      +                if times is not None and times > 0 and completed >= times:
                           # Remove the job (limit reached)
                           jobs.pop(i)
                           save_jobs(jobs)
    • tests/cron/test_jobs.py
      @@ -313,6 +313,24 @@ def test_repeat_limit_removes_job(self, tmp_cron_dir):
               # Job should be removed after hitting repeat limit
               assert get_job(job["id"]) is None
       
      +    def test_repeat_negative_one_is_infinite(self, tmp_cron_dir):
      +        # LLMs often pass repeat=-1 to mean "infinite/forever".
      +        # The job must NOT be deleted after runs when repeat <= 0.
      +        job = create_job(prompt="Forever", schedule="every 1h", repeat=-1)
      +        # -1 should be normalised to None (infinite) at create time
      +        assert job["repeat"]["times"] is None
      +        # Running it multiple times should never delete it
      +        for _ in range(3):
      +            mark_job_run(job["id"], success=True)
      +            assert get_job(job["id"]) is not None, "job was deleted after run despite infinite repeat"
      +
      +    def test_repeat_zero_is_infinite(self, tmp_cron_dir):
      +        # repeat=0 should also be treated as None (infinite), not "run zero times".
      +        job = create_job(prompt="ZeroRepeat", schedule="every 1h", repeat=0)
      +        assert job["repeat"]["times"] is None
      +        mark_job_run(job["id"], success=True)
      +        assert get_job(job["id"]) is not None
      +
           def test_error_status(self, tmp_cron_dir):
               job = create_job(prompt="Fail", schedule="every 1h")
               mark_job_run(job["id"], success=False, error="timeout")
    • tools/cronjob_tools.py
      @@ -266,8 +266,10 @@ def cronjob(
                   if base_url is not None:
                       updates["base_url"] = _normalize_optional_job_value(base_url, strip_trailing_slash=True)
                   if repeat is not None:
      +                # Normalize: treat 0 or negative as None (infinite)
      +                normalized_repeat = None if repeat <= 0 else repeat
                       repeat_state = dict(job.get("repeat") or {})
      -                repeat_state["times"] = repeat
      +                repeat_state["times"] = normalized_repeat
                       updates["repeat"] = repeat_state
                   if schedule is not None:
                       parsed_schedule = parse_schedule(schedule)

1 PR closed without merging

From the close comments: superseded by another PR 1

  • #2616 fix(cron): normalize repeat=-1 (LLM 'forever' convention) to None to prevent auto-deletion · superseded by another PR → #2612
Closed issues · 1
  • Closed#2611[Bug]: Cron jobs getting deleted after one run
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.67: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
cron
Merged PRs
#2612
Closed issues
1
Attempts
1 PR closed without merging
Fix link checked
23 Mar 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.

For your agent

The same facts, typed.