Skip to content

[Bug]: hermes cron list crashes with AttributeError on jobs created via cronjob MCP tool #28662

@bhashem

Description

@bhashem

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/cliCLI entry point, hermes_cli/, setup wizardcomp/cronCron scheduler and job managementtype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions