From d1d956da32a7b11c1c2f1c3ddc59524ed2922601 Mon Sep 17 00:00:00 2001 From: vominh1919 Date: Sat, 25 Apr 2026 21:26:14 +0700 Subject: [PATCH 1/3] fix: handle _find_all_skills() exceptions in /api/skills endpoint The /api/skills endpoint calls _find_all_skills() without a try/except. If skill discovery raises (e.g. corrupted skills directory, permission error), the endpoint returns a 500 Internal Server Error. Wrap the call in try/except and return an empty list on failure, matching the pattern used by _list_all_skills() in skills_config.py. Fixes NousResearch/hermes-agent#15486 --- hermes_cli/web_server.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 0a7657f337568..b0d0ee4d9e08b 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -1900,7 +1900,10 @@ async def get_skills(): from hermes_cli.skills_config import get_disabled_skills config = load_config() disabled = get_disabled_skills(config) - skills = _find_all_skills(skip_disabled=True) + try: + skills = _find_all_skills(skip_disabled=True) + except Exception: + skills = [] for s in skills: s["enabled"] = s["name"] not in disabled return skills From 183d824177fb63d783b1cefa5140670369a8f008 Mon Sep 17 00:00:00 2001 From: vominh1919 Date: Sat, 25 Apr 2026 21:26:18 +0700 Subject: [PATCH 2/3] fix: normalize repeat field in update_job() to prevent TypeError update_job() merges updates directly via {**job, **updates}, which overwrites the repeat dict structure {"times": N, "completed": 0} with a raw integer when the API passes repeat as an int. This causes mark_job_run() to crash with TypeError when it tries to subscript the integer: job["repeat"]["completed"]. Add the same normalization that create_job() applies, converting raw integers to the expected dict structure. Fixes NousResearch/hermes-agent#15582 --- cron/jobs.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cron/jobs.py b/cron/jobs.py index 06d782888f7ef..71391feba99af 100644 --- a/cron/jobs.py +++ b/cron/jobs.py @@ -492,6 +492,17 @@ def update_job(job_id: str, updates: Dict[str, Any]) -> Optional[Dict[str, Any]] continue updated = _apply_skill_fields({**job, **updates}) + + # Normalize repeat field if present in updates + if "repeat" in updates: + raw_repeat = updated["repeat"] + if isinstance(raw_repeat, (int, float)): + val = int(raw_repeat) if raw_repeat > 0 else None + updated["repeat"] = {"times": val, "completed": 0} + elif isinstance(raw_repeat, dict): + raw_repeat.setdefault("times", None) + raw_repeat.setdefault("completed", 0) + schedule_changed = "schedule" in updates if "skills" in updates or "skill" in updates: From e3eb380edcd93bcf650fea6f00cb804888a22a22 Mon Sep 17 00:00:00 2001 From: vominh1919 Date: Sat, 25 Apr 2026 21:26:21 +0700 Subject: [PATCH 3/3] fix: reset _summary_failure_cooldown_until on session reset ContextCompressor.on_session_reset() clears all per-session state except _summary_failure_cooldown_until. When a transient summary error (network timeout, rate limit) sets a cooldown, and the user then runs /new or /reset, the cooldown persists into the new session, silently blocking context compression. Fixes NousResearch/hermes-agent#15547 --- agent/context_compressor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/agent/context_compressor.py b/agent/context_compressor.py index 34ec5091b1c6f..0873ad90c56dd 100644 --- a/agent/context_compressor.py +++ b/agent/context_compressor.py @@ -208,6 +208,7 @@ def on_session_reset(self) -> None: self._previous_summary = None self._last_compression_savings_pct = 100.0 self._ineffective_compression_count = 0 + self._summary_failure_cooldown_until = 0.0 def update_model( self,