hermes - ✅(Solved) Fix API job repeat updates corrupt cron repeat state [2 pull requests, 2 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#15582Fetched 2026-04-26 05:26:26
View on GitHub
Comments
2
Participants
3
Timeline
8
Reactions
0
Timeline (top)
labeled ×4commented ×2cross-referenced ×2

Error Message

AttributeError: 'int' object has no attribute 'get'

Fix Action

Fix / Workaround

PATCH /api/jobs/{job_id} accepts repeat as an allowed update field, but forwards the raw integer directly to cron.jobs.update_job(). create_job() stores repeat as a dict ({"times": n, "completed": 0}), while mark_job_run() assumes job["repeat"] is that dict and calls .get() on it. Updating a job's repeat count via the API can therefore corrupt the job record and crash the next run bookkeeping.

PR fix notes

PR #15590: fix(cron): normalize repeat integer updates to dict format

Description (problem / solution / changelog)

Summary

Fixes cron.jobs.update_job() to normalize raw integer repeat values from API PATCH requests into the internal dict format ({"times": n, "completed": count}). Previously, the API would forward raw integers directly to update_job(), causing mark_job_run() to crash with AttributeError: 'int' object has no attribute 'get'.

Root Cause

  • create_job() stores repeat as a dict: {"times": n, "completed": 0}
  • API PATCH /api/jobs/{job_id} accepts repeat as a raw integer (e.g., {"repeat": 2})
  • update_job() in cron/jobs.py merged updates directly without special handling for repeat
  • mark_job_run() assumed job["repeat"] is a dict and called .get("completed") on it
  • After an API update, job["repeat"] became an integer, causing mark_job_run() to crash on next run

Fix

Add repeat normalization in update_job(), mirroring the existing schedule field handling:

  • If repeat is an integer > 0, normalize to {"times": value, "completed": existing_count}
  • If repeat is <= 0, normalize to None (matching create_job() behavior)
  • Preserve existing completed count from job's current repeat state

Affected Files

  • cron/jobs.py - Add repeat_changed flag and normalization logic in update_job()
  • tests/gateway/test_api_server_jobs_update_repeat.py - Add regression coverage for update path

Regression Coverage

  • test_update_job_repeat_normalizes_integer_to_dict() - Verifies integer→dict normalization and completed preservation
  • test_update_job_repeat_negative_or_zero_is_rejected() - Verifies repeat <= 0 is normalized to None

Testing

# New regression tests pass
python -m pytest tests/gateway/test_api_server_jobs_update_repeat.py -xvs
# 2 passed

# Existing cron tests pass
bash scripts/run_tests.sh tests/cron/test_jobs.py
# 61 passed

# Existing API server tests pass
bash scripts/run_tests.sh tests/gateway/test_api_server_jobs.py
# 35 passed, 35 warnings

Closes #15582

Changed files

  • cron/jobs.py (modified, +15/-0)
  • gateway/platforms/whatsapp.py (modified, +4/-1)
  • tests/gateway/test_api_server_jobs_update_repeat.py (added, +109/-0)
  • tests/tools/test_file_tools.py (modified, +37/-0)
  • tools/file_tools.py (modified, +6/-6)

PR #15682: fix: normalize repeat field in update_job() to prevent TypeError (#15582)

Description (problem / solution / changelog)

Problem

update_job() merges updates via {**job, **updates}, which blindly overwrites the repeat dict structure {"times": N, "completed": 0} with a raw integer when the API passes repeat as an int.

This causes mark_job_run() to crash with TypeError: 'int' object is not subscriptable when it tries job["repeat"]["completed"].

Fix

Add repeat field normalization in update_job(), mirroring what create_job() already applies:

  • If repeat is int/float: convert to {"times": val, "completed": 0} dict
  • If repeat is a dict: ensure "times" and "completed" keys exist via setdefault

Before vs After

ScenarioBeforeAfter
PATCH /api/jobs/{id} with repeat: 5Corrupts state, mark_job_run() crashesNormalizes to {"times": 5, "completed": 0}

Fixes NousResearch/hermes-agent#15582

Changed files

  • cron/jobs.py (modified, +11/-0)
  • hermes_cli/web_server.py (modified, +4/-1)

Code Example

import tempfile, pathlib
from cron import jobs

with tempfile.TemporaryDirectory() as td:
    tmp = pathlib.Path(td)
    jobs.CRON_DIR = tmp / "cron"
    jobs.JOBS_FILE = jobs.CRON_DIR / "jobs.json"
    jobs.OUTPUT_DIR = jobs.CRON_DIR / "output"

    job = jobs.create_job(prompt="p", schedule="every 1h", name="n", repeat=5, deliver="local")
    print(job["repeat"])  # {'times': 5, 'completed': 0}

    updated = jobs.update_job(job["id"], {"repeat": 2})
    print(updated["repeat"], type(updated["repeat"]).__name__)  # 2 int

    jobs.mark_job_run(job["id"], success=True)

---

AttributeError: 'int' object has no attribute 'get'
RAW_BUFFERClick to expand / collapse

Bug

PATCH /api/jobs/{job_id} accepts repeat as an allowed update field, but forwards the raw integer directly to cron.jobs.update_job(). create_job() stores repeat as a dict ({"times": n, "completed": 0}), while mark_job_run() assumes job["repeat"] is that dict and calls .get() on it. Updating a job's repeat count via the API can therefore corrupt the job record and crash the next run bookkeeping.

Affected files / lines

  • gateway/platforms/api_server.py:1892_UPDATE_ALLOWED_FIELDS includes "repeat".
  • gateway/platforms/api_server.py:2008-2022 — update handler whitelists fields and calls _cron_update(job_id, sanitized) without validating or normalizing repeat.
  • cron/jobs.py:507update_job() merges raw updates into the job.
  • cron/jobs.py:622-627mark_job_run() treats job["repeat"] as a dict and calls job["repeat"].get(...).
  • tests/gateway/test_api_server_jobs.py:207-220 validates invalid repeat on create, but there is no equivalent update-path regression coverage.

Minimal reproduction

From the repo with the venv active, this uses a temp cron DB and does not touch the real jobs file:

import tempfile, pathlib
from cron import jobs

with tempfile.TemporaryDirectory() as td:
    tmp = pathlib.Path(td)
    jobs.CRON_DIR = tmp / "cron"
    jobs.JOBS_FILE = jobs.CRON_DIR / "jobs.json"
    jobs.OUTPUT_DIR = jobs.CRON_DIR / "output"

    job = jobs.create_job(prompt="p", schedule="every 1h", name="n", repeat=5, deliver="local")
    print(job["repeat"])  # {'times': 5, 'completed': 0}

    updated = jobs.update_job(job["id"], {"repeat": 2})
    print(updated["repeat"], type(updated["repeat"]).__name__)  # 2 int

    jobs.mark_job_run(job["id"], success=True)

Actual result:

AttributeError: 'int' object has no attribute 'get'

Expected behavior

Updating repeat through /api/jobs/{job_id} should either:

  1. reject invalid/non-positive/non-integer repeat values the same way create does, and normalize valid integers into the internal repeat dict shape while preserving completed, or
  2. remove repeat from API update fields until the backend supports safe repeat updates.

The scheduler/run bookkeeping should not be able to persist a malformed repeat value through a public API endpoint.

Suggested investigation direction

Add update-path tests in tests/gateway/test_api_server_jobs.py and/or cron/jobs.py that cover repeat=2, repeat=0, and non-integer repeat updates. Then normalize in update_job() or validate/transform in APIServerAdapter._handle_update_job() before calling _cron_update.

extent analysis

TL;DR

Update the repeat field validation and normalization in the APIServerAdapter._handle_update_job() method to ensure it matches the internal repeat dict shape.

Guidance

  • Validate the repeat value in the APIServerAdapter._handle_update_job() method to ensure it's a positive integer.
  • Normalize the repeat value into the internal dict shape ({"times": n, "completed": 0}) before calling _cron_update.
  • Add update-path tests in tests/gateway/test_api_server_jobs.py to cover different repeat update scenarios.
  • Consider removing repeat from the allowed update fields until the backend supports safe repeat updates.

Example

def _handle_update_job(self, job_id, updates):
    if "repeat" in updates:
        repeat = updates["repeat"]
        if not isinstance(repeat, int) or repeat <= 0:
            # Handle invalid repeat value
            pass
        else:
            updates["repeat"] = {"times": repeat, "completed": 0}
    # ...

Notes

The current implementation assumes that the repeat value is always a dict, which can lead to errors when updating the job. Normalizing the repeat value before updating the job can prevent these errors.

Recommendation

Apply a workaround by validating and normalizing the repeat value in the APIServerAdapter._handle_update_job() method. This will ensure that the repeat value is always in the correct format, preventing errors when updating the job.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Updating repeat through /api/jobs/{job_id} should either:

  1. reject invalid/non-positive/non-integer repeat values the same way create does, and normalize valid integers into the internal repeat dict shape while preserving completed, or
  2. remove repeat from API update fields until the backend supports safe repeat updates.

The scheduler/run bookkeeping should not be able to persist a malformed repeat value through a public API endpoint.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix API job repeat updates corrupt cron repeat state [2 pull requests, 2 comments, 3 participants]