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
- 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')
- Run
hermes cron list
- 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.
Bug Report:
hermes cron listcrashes with AttributeError on jobs created via thecronjobMCP toolRepository: https://github.com/NousResearch/hermes-agent
File affected:
hermes_cli/cron.pyCommand affected:
hermes cron listSeverity: Medium — crashes the entire
cron listoutput, truncating any jobs that appear after the offending job in the listSummary
hermes cron listthrowsAttributeErrorwhen the jobs list contains any job whoseschedulefield is a plain string or whoserepeatfield isNone. Both conditions occur on every job created via thecronjobMCP tool (as opposed to jobs created through the Hermes CLI wizard).Bug 1 —
schedulefield assumed to always be a dictLocation:
hermes_cli/cron.py, line 61Failing line:
Error:
Root cause:
The code assumes
scheduleis always a dict. But when a job is created via thecronjobMCP tool, the scheduler storesscheduleas a plain string (e.g."0 7 * * 1"). Calling.get("value")on a string raisesAttributeError.There is also a secondary issue in the same line: even when
scheduleIS 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:
This handles all three real-world cases:
schedule_displayset → use it directly (existing fast path)scheduleis a plain string → use it directlyscheduleis a dict → try keysdisplay, thenexpr, thenvalue, then"?"Bug 2 —
repeatfield assumed to never beNoneLocation:
hermes_cli/cron.py, lines 65–67Failing lines:
Error:
Root cause:
When a job is created via the
cronjobMCP tool withrepeat=None(the default for infinite recurring jobs),job.get("repeat", {})returnsNonerather than{}— because the key IS present in the dict, it just maps toNone. Calling.get()onNoneraisesAttributeError.Proposed fix:
Using
or {}ensuresNoneis converted to an empty dict. Theisinstanceguards are a further safety net for any other unexpected types.How to reproduce
cronjobMCP tool (via Claude/Hermes) to create any cron job — e.g.:hermes cron listJobs created through the CLI wizard (
hermes cron) do not trigger the bug because the wizard writesscheduleas a dict withdisplay/exprkeys, andrepeatas{"completed": N, "times": None}rather than bareNone.Impact
cronjobMCP tool (a primary and documented workflow) gets a brokenhermes cron listoutputFix summary (both bugs, 6 lines changed)
Both fixes are already applied and verified working in a local checkout.