hermes - ✅(Solved) Fix [Bug]: hermes cron list crashes with AttributeError on jobs created via cronjob MCP tool [2 pull requests, 1 comments, 2 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#28662Fetched 2026-05-20 04:02:42
View on GitHub
Comments
1
Participants
2
Timeline
9
Reactions
0
Author
Participants
Timeline (top)
labeled ×4cross-referenced ×3commented ×1referenced ×1

hermes cron list throws AttributeError when the jobs list contains any job whose schedule field is a plain string or whose repeat field is None. Both conditions occur on every job created via the cronjob MCP tool (as opposed to jobs created through the Hermes CLI wizard).


Error Message

AttributeError: 'str' object has no attribute 'get' File "hermes_cli/cron.py", line 61, in cron_list schedule = job.get("schedule_display", job.get("schedule", {}).get("value", "?"))

Root Cause

Root cause:
The code assumes schedule is always a dict. But when a job is created via the cronjob MCP tool, the scheduler stores schedule as a plain string (e.g. "0 7 * * 1"). Calling .get("value") on a string raises AttributeError.

Fix Action

Fixed

PR fix notes

PR #28771: fix(cron): tolerate null repeat in cron list

Description (problem / solution / changelog)

Summary

hermes cron list assumes repeat is always a dict and that schedule is always dict-shaped when schedule_display is missing. MCP-created or hand-edited jobs can violate both assumptions and crash the list command.

Closes #28662.

Changes

  • make cron_list() handle repeat=None and other non-dict repeat values
  • make cron_list() fall back cleanly when schedule is a plain string instead of a dict
  • add a CLI regression test covering the mixed legacy/MCP-shaped job payload

Testing

  • uv run --extra dev pytest tests/hermes_cli/test_cron.py -q

Changed files

  • hermes_cli/cron.py (modified, +18/-2)
  • tests/hermes_cli/test_cron.py (modified, +24/-1)

PR #10: fix(cron): handle string schedule and None repeat in cron list (#28662)

Description (problem / solution / changelog)

🟢 Merge order: 4 / 12 — small, focused type guards, low risk

Closes #28662 (P2)

Problem

hermes cron list crashes with AttributeError on jobs created via the cronjob MCP tool because schedule is a plain string (not dict) and repeat can be None.

Fix

  • Handle schedule as string, dict, or with nested keys (display, expr, value)
  • Use or {} and isinstance guard for repeat field

Risk assessment

FactorRating
Lines changed8/-4
New codetype guards only
Side effectsNone — purely defensive
Revert complexityTrivial

Files changed

  • hermes_cli/cron.py (+8/-4)

Changed files

  • hermes_cli/cron.py (modified, +8/-4)

Code Example

schedule = job.get("schedule_display", job.get("schedule", {}).get("value", "?"))

---

AttributeError: 'str' object has no attribute 'get'
  File "hermes_cli/cron.py", line 61, in cron_list
    schedule = job.get("schedule_display", job.get("schedule", {}).get("value", "?"))

---

_sched = job.get("schedule", {})
schedule = job.get("schedule_display") or (
    _sched if isinstance(_sched, str) else
    _sched.get("display", _sched.get("expr", _sched.get("value", "?")))
)

---

repeat_info = job.get("repeat", {})
repeat_times = repeat_info.get("times")
repeat_completed = repeat_info.get("completed", 0)

---

AttributeError: 'NoneType' object has no attribute 'get'
  File "hermes_cli/cron.py", line 66, in cron_list
    repeat_times = repeat_info.get("times")

---

repeat_info = job.get("repeat") or {}
repeat_times = repeat_info.get("times") if isinstance(repeat_info, dict) else None
repeat_completed = repeat_info.get("completed", 0) if isinstance(repeat_info, dict) else 0

---

cronjob(action='create', name='test-job', prompt='say hello', schedule='0 7 * * 1')

---

# cron.py line ~61: schedule field
_sched = job.get("schedule", {})
schedule = job.get("schedule_display") or (
    _sched if isinstance(_sched, str) else
    _sched.get("display", _sched.get("expr", _sched.get("value", "?")))
)

# cron.py lines ~65-67: repeat field
repeat_info = job.get("repeat") or {}
repeat_times = repeat_info.get("times") if isinstance(repeat_info, dict) else None
repeat_completed = repeat_info.get("completed", 0) if isinstance(repeat_info, dict) else 0
RAW_BUFFERClick to expand / collapse

Bug Report: hermes cron list crashes with AttributeError on jobs created via the cronjob MCP tool

Repository: https://github.com/NousResearch/hermes-agent
File affected: hermes_cli/cron.py
Command affected: hermes cron list
Severity: Medium — crashes the entire cron list output, truncating any jobs that appear after the offending job in the list


Summary

hermes cron list throws AttributeError when the jobs list contains any job whose schedule field is a plain string or whose repeat field is None. Both conditions occur on every job created via the cronjob MCP tool (as opposed to jobs created through the Hermes CLI wizard).


Bug 1 — schedule field assumed to always be a dict

Location: hermes_cli/cron.py, line 61

Failing line:

schedule = job.get("schedule_display", job.get("schedule", {}).get("value", "?"))

Error:

AttributeError: 'str' object has no attribute 'get'
  File "hermes_cli/cron.py", line 61, in cron_list
    schedule = job.get("schedule_display", job.get("schedule", {}).get("value", "?"))

Root cause:
The code assumes schedule is always a dict. But when a job is created via the cronjob MCP tool, the scheduler stores schedule as a plain string (e.g. "0 7 * * 1"). Calling .get("value") on a string raises AttributeError.

There is also a secondary issue in the same line: even when schedule IS a dict, the code looks for key "value" — but the actual schema used by the CLI/scheduler stores it under "display" or "expr", not "value". So the existing fallback would silently return "?" for every job even in the non-crashing case.

Proposed fix:

_sched = job.get("schedule", {})
schedule = job.get("schedule_display") or (
    _sched if isinstance(_sched, str) else
    _sched.get("display", _sched.get("expr", _sched.get("value", "?")))
)

This handles all three real-world cases:

  • schedule_display set → use it directly (existing fast path)
  • schedule is a plain string → use it directly
  • schedule is a dict → try keys display, then expr, then value, then "?"

Bug 2 — repeat field assumed to never be None

Location: hermes_cli/cron.py, lines 65–67

Failing lines:

repeat_info = job.get("repeat", {})
repeat_times = repeat_info.get("times")
repeat_completed = repeat_info.get("completed", 0)

Error:

AttributeError: 'NoneType' object has no attribute 'get'
  File "hermes_cli/cron.py", line 66, in cron_list
    repeat_times = repeat_info.get("times")

Root cause:
When a job is created via the cronjob MCP tool with repeat=None (the default for infinite recurring jobs), job.get("repeat", {}) returns None rather than {} — because the key IS present in the dict, it just maps to None. Calling .get() on None raises AttributeError.

Proposed fix:

repeat_info = job.get("repeat") or {}
repeat_times = repeat_info.get("times") if isinstance(repeat_info, dict) else None
repeat_completed = repeat_info.get("completed", 0) if isinstance(repeat_info, dict) else 0

Using or {} ensures None is converted to an empty dict. The isinstance guards are a further safety net for any other unexpected types.


How to reproduce

  1. Use the cronjob MCP tool (via Claude/Hermes) to create any cron job — e.g.:
    cronjob(action='create', name='test-job', prompt='say hello', schedule='0 7 * * 1')
  2. Run hermes cron list
  3. Observe crash

Jobs created through the CLI wizard (hermes cron) do not trigger the bug because the wizard writes schedule as a dict with display/expr keys, and repeat as {"completed": N, "times": None} rather than bare None.


Impact

  • Any user who creates cron jobs via the cronjob MCP tool (a primary and documented workflow) gets a broken hermes cron list output
  • The crash occurs mid-loop, so any jobs listed after the offending one are silently dropped from the display
  • The fix is minimal and backward-compatible — no schema changes required

Fix summary (both bugs, 6 lines changed)

# cron.py line ~61: schedule field
_sched = job.get("schedule", {})
schedule = job.get("schedule_display") or (
    _sched if isinstance(_sched, str) else
    _sched.get("display", _sched.get("expr", _sched.get("value", "?")))
)

# cron.py lines ~65-67: repeat field
repeat_info = job.get("repeat") or {}
repeat_times = repeat_info.get("times") if isinstance(repeat_info, dict) else None
repeat_completed = repeat_info.get("completed", 0) if isinstance(repeat_info, dict) else 0

Both fixes are already applied and verified working in a local checkout.

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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING