From 8d06cabed60b70d30562e998eda2d0c53947473a Mon Sep 17 00:00:00 2001 From: aim-ma-sourcing Date: Fri, 22 May 2026 12:48:27 +0900 Subject: [PATCH] fix(cron): handle bare-list jobs.json in load_jobs() load_jobs() assumed jobs.json is always a dict {"jobs": [...]}, but bare list format [...] can appear from: - distribution installs (shutil.copytree copies cron/ as-is) - quick snapshot restores (raw file copy) - older hermes versions or manual edits This caused 'list' object has no attribute 'get' on every cronjob(action='list') call, breaking all cron management until the file was manually fixed. curator_backup.py already handled both formats (line 424), but the primary load_jobs() did not. Now load_jobs() detects bare lists and auto-repairs them to canonical {"jobs": [...]} format on read. --- cron/jobs.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cron/jobs.py b/cron/jobs.py index 6d7845c496c25..c3f287b95aa11 100644 --- a/cron/jobs.py +++ b/cron/jobs.py @@ -410,15 +410,25 @@ def load_jobs() -> List[Dict[str, Any]]: try: with open(JOBS_FILE, 'r', encoding='utf-8') as f: data = json.load(f) + # Accept both {"jobs": [...]} and bare [...] formats. + # Bare lists can appear from older versions, distribution installs, + # or snapshot restores. Auto-repair to canonical format. + if isinstance(data, list): + logger.warning("Auto-repaired jobs.json (bare list → {\"jobs\": [...]})") + save_jobs(data) + return data return data.get("jobs", []) except json.JSONDecodeError: # Retry with strict=False to handle bare control chars in string values try: with open(JOBS_FILE, 'r', encoding='utf-8') as f: data = json.loads(f.read(), strict=False) - jobs = data.get("jobs", []) + if isinstance(data, list): + jobs = data + else: + jobs = data.get("jobs", []) if jobs: - # Auto-repair: rewrite with proper escaping + # Auto-repair: rewrite with proper escaping and canonical format save_jobs(jobs) logger.warning("Auto-repaired jobs.json (had invalid control characters)") return jobs