NousResearch/hermes-agent · error · ValueError
Invalid cron job id for output path: {job_id!r}
Error message
Invalid cron job id for output path: {job_id!r} What it means
Security ValueError from _job_output_dir in cron/jobs.py: the job id is used directly as a filesystem path component under the cron OUTPUT_DIR, so ids containing '/', '\\', '.', '..', that are empty, or that are absolute/UNC/drive-prefixed are rejected to prevent output writes and deletes from escaping the cron output sandbox. This variant catches separators, empty/dot ids.
Source
Thrown at cron/jobs.py:391
# Fields on a cron job that must never change after creation. ``id`` is used
# as a filesystem path component under ``OUTPUT_DIR``; allowing it to be
# updated lets an unsafe value (``../escape``, absolute path, nested) leak
# into output writes/deletes.
_IMMUTABLE_JOB_FIELDS = frozenset({"id"})
def _job_output_dir(job_id: str) -> Path:
"""Resolve a job's output directory, rejecting any path-escape attempt.
Job IDs are filesystem path components under ``OUTPUT_DIR``. A legacy or
crafted ID containing ``..``, absolute paths, or nested separators would
allow output writes/deletes to escape the cron output sandbox. Reject
anything that isn't a single safe path component.
"""
text = str(job_id or "").strip()
if not text or text in {".", ".."} or "/" in text or "\\" in text:
raise ValueError(f"Invalid cron job id for output path: {job_id!r}")
if Path(text).is_absolute() or Path(text).drive:
raise ValueError(f"Invalid cron job id for output path: {job_id!r}")
return _current_cron_store().output_dir / text
def _normalize_skill_list(skill: Optional[str] = None, skills: Optional[Any] = None) -> List[str]:
"""Normalize legacy/single-skill and multi-skill inputs into a unique ordered list."""
if skills is None:
raw_items = [skill] if skill else []
elif isinstance(skills, str):
raw_items = [skills]
else:
raw_items = list(skills)
normalized: List[str] = []
for item in raw_items:
text = str(item or "").strip()
if text and text not in normalized:View on GitHub (pinned to c896c09c42)
Solutions
- Pass the plain alphanumeric job id (as returned by create_job), not a path or composite string
- Sanitize before calling: reject ids containing os.sep, '..', or empty strings
- If a legacy store really contains such ids, re-create those jobs to obtain clean ids
Example fix
# before
jobs.get_job_output('../config')
# ValueError: Invalid cron job id for output path: '../config'
# after
jobs.get_job_output('a1b2c3d4') Defensive patterns
Strategy: validation
Validate before calling
import re
_SAFE_ID = re.compile(r"^[A-Za-z0-9_-]+$")
def is_safe_job_id(job_id: str) -> bool:
t = str(job_id or "").strip()
return bool(_SAFE_ID.match(t)) and t not in {".", ".."} Type guard
from pathlib import Path
def is_safe_job_id(job_id: str) -> bool:
t = str(job_id or "").strip()
if not t or t in {".", ".."} or "/" in t or "\\" in t:
return False
return not (Path(t).is_absolute() or Path(t).drive) Try / catch
try:
out = jobs.get_job_output(job_id)
except ValueError as e:
if str(e).startswith("Invalid cron job id"):
raise KeyError(f"no such job: {job_id!r} (bad id)") from e
raise Prevention
- Treat job ids as opaque tokens from create_job; never build them from paths
- Validate ids with a strict ^[A-Za-z0-9_-]+$ pattern before any output call
- Reject '..' and separator-containing ids at the API boundary of your own tooling
When it happens
Trigger: Calling a job-output API (list/save/delete output) with job_id='../hermes', job_id='a/b', job_id='', or job_id='.' — typically from hand-crafted input, a migrated legacy store with unusual ids, or unvalidated caller data.
Common situations: Passing a composite key or file path as the job id; a client joining values with '/'; legacy jobs created before ids were constrained; fuzzing/probing attempts on the path boundary.
Related errors
- ${label} exited and ${dashboardIndexUrl(baseUrl)} is served
- 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/ac8ef9aacf0286ae.
Report an issue: GitHub.