NousResearch/hermes-agent · warning · ValueError
Invalid cron expression '{schedule}': {e}
Error message
Invalid cron expression '{schedule}': {e} What it means
ValueError from parse_schedule's cron branch: croniter IS available, the string shaped like a cron expression (5-6 fields of digits/*/-/,/), but croniter's own validation rejected it. The underlying croniter exception text is appended, typically naming the invalid field (e.g. minute values out of range, bad step).
Source
Thrown at cron/jobs.py:656
return {
"kind": "interval",
"minutes": minutes,
"display": f"every {minutes}m"
}
# Check for cron expression (5 or 6 space-separated fields)
# Cron fields: minute hour day month weekday [year]
parts = schedule.split()
if len(parts) >= 5 and all(
re.match(r'^[\d\*\-,/]+$', p) for p in parts[:5]
):
if not _ensure_croniter():
raise ValueError("Cron expressions require 'croniter' package. Install with: pip install croniter")
# Validate cron expression
try:
croniter(schedule)
except Exception as e:
raise ValueError(f"Invalid cron expression '{schedule}': {e}")
return {
"kind": "cron",
"expr": schedule,
"display": schedule
}
# ISO timestamp (contains T or looks like date)
if 'T' in schedule or re.match(r'^\d{4}-\d{2}-\d{2}', schedule):
try:
# Parse and validate
dt = datetime.fromisoformat(schedule.replace('Z', '+00:00'))
# Make naive timestamps timezone-aware at parse time so the stored
# value doesn't depend on the system timezone matching at check time.
#
# Anchor to the CONFIGURED Hermes timezone, not the server's local
# timezone. The due-check (`get_due_jobs`) compares `next_run_at`
# against `hermes_time.now()`, which uses the configured zone. If a
# naive "20:07" were interpreted as server-local (e.g. UTC) whileView on GitHub (pinned to c896c09c42)
Solutions
- Fix the field croniter names in the message — minute 0-59, hour 0-23, dom 1-31, month 1-12, dow 0-7
- Validate locally first: from croniter import croniter; croniter(expr) in a try/except before submitting
- For simple daily jobs consider a duration or blueprint schedule instead of raw cron
Example fix
# before create_job(schedule="61 9 * * *") # ValueError: Invalid cron expression '61 9 * * *': [minute] ... # after create_job(schedule="1 9 * * *")
Defensive patterns
Strategy: validation
Validate before calling
from croniter import croniter
def is_valid_cron(expr: str) -> bool:
try:
croniter(expr)
return True
except Exception:
return False Try / catch
try:
parsed = parse_schedule(expr)
except ValueError as e:
if str(e).startswith("Invalid cron expression"):
expr = fix_cron_fields(expr) # e.g. clamp minute 61 -> 1, prompt user
parsed = parse_schedule(expr)
else:
raise Prevention
- Validate with croniter() in a try/except before create_job
- Keep fields in range: minute 0-59, hour 0-23, dom 1-31, month 1-12, dow 0-7
- Prefer the blueprint/duration schedule forms for simple recurring jobs
When it happens
Trigger: create_job(schedule='61 9 * * *') (minute > 59), schedule='0 25 * * *' (hour > 23), schedule='*/0 * * * *' (zero step), or malformed field lists like '1,,2 * * * *'.
Common situations: Hand-written cron expressions with out-of-range fields; 6-field expressions with a bad year; copy-paste crontabs with usernames or @-tokens (those don't match the field regex and route elsewhere or fail differently); assuming 24:00 or 32nd day are accepted.
Related errors
- Invalid duration: '{s}'. Use format like '30m', '2h', or '1d
- Preview mode — launching is disabled.
- no install root
- ${isPosix ? 'install.sh --manifest' : 'install.ps1 -Manifest
- Remote connection is not ready yet. Try again in a moment.
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/97ddd07a27a580b9.
Report an issue: GitHub.