fix(cron): stop silent cron-job loss across update (disk-cleanup + snapshot restore)#34840
Merged
Conversation
Config-version migrations have been observed to leave cron/jobs.json valid-but-empty after `hermes update`, silently dropping every scheduled job (#34600). The existing malformed-shape guards in cron/jobs.py don't catch this because {"jobs": []} is valid JSON. Add restore_cron_jobs_if_emptied() as a post-migration safety net: if the live cron/jobs.json now has zero jobs while the pre-update snapshot held one or more, restore the snapshot copy in place and warn loudly. The check is conservative — it only restores on unambiguous evidence of loss (snapshot had jobs, live file readable-and-empty), so a user who genuinely cleared their jobs is never second-guessed and an unreadable live file is left untouched so real corruption still surfaces. Wired into _cmd_update_impl after migrate_config(), reusing the existing pre-update quick snapshot (which already captures cron/jobs.json). Closes #34600
Maps the cherry-picked commit's noreply email to the GitHub login so the release attribution / CI author check passes.
Collaborator
This was referenced May 29, 2026
Closed
13 tasks
13 tasks
teknium1
pushed a commit
that referenced
this pull request
Jun 4, 2026
) quick() and dry_run() previously trusted the stored category from tracked.json without re-validating at delete time. Stale entries from before #34840 could carry category="cron-output" for cron control-plane paths (e.g. cron/jobs.json), causing quick() to delete the live scheduler registry. Fix: - Fix guess_category() to only classify cron/output/** as cron-output (was classifying ALL cron/* paths, missing the #34840 fix). - Re-validate cron-output entries via guess_category() at delete time in quick() and dry_run(); stale entries that are no longer classified as cron-output are skipped and removed from tracked.json. - Add _is_protected_cron_path() as a hard defense-in-depth guard that blocks deletion of cron/cronjobs directories and known control-plane files (jobs.json, .tick.lock) regardless of stored category. - Update test_cron_subtree_categorised to match fixed guess_category (only cron/output/* is cron-output, not all of cron/). Tests: add 5 regression tests in TestStaleCronEntryMigration.
teknium1
pushed a commit
that referenced
this pull request
Jun 4, 2026
) quick() and dry_run() previously trusted the stored category from tracked.json without re-validating at delete time. Stale entries from before #34840 could carry category="cron-output" for cron control-plane paths (e.g. cron/jobs.json), causing quick() to delete the live scheduler registry. Fix: - Fix guess_category() to only classify cron/output/** as cron-output (was classifying ALL cron/* paths, missing the #34840 fix). - Re-validate cron-output entries via guess_category() at delete time in quick() and dry_run(); stale entries that are no longer classified as cron-output are skipped and removed from tracked.json. - Add _is_protected_cron_path() as a hard defense-in-depth guard that blocks deletion of cron/cronjobs directories and known control-plane files (jobs.json, .tick.lock) regardless of stored category. - Update test_cron_subtree_categorised to match fixed guess_category (only cron/output/* is cron-output, not all of cron/). Tests: add 5 regression tests in TestStaleCronEntryMigration.
waym0reom3ga
pushed a commit
to waym0reom3ga/autolycus-agent
that referenced
this pull request
Jun 4, 2026
…sResearch#37721) quick() and dry_run() previously trusted the stored category from tracked.json without re-validating at delete time. Stale entries from before NousResearch#34840 could carry category="cron-output" for cron control-plane paths (e.g. cron/jobs.json), causing quick() to delete the live scheduler registry. Fix: - Fix guess_category() to only classify cron/output/** as cron-output (was classifying ALL cron/* paths, missing the NousResearch#34840 fix). - Re-validate cron-output entries via guess_category() at delete time in quick() and dry_run(); stale entries that are no longer classified as cron-output are skipped and removed from tracked.json. - Add _is_protected_cron_path() as a hard defense-in-depth guard that blocks deletion of cron/cronjobs directories and known control-plane files (jobs.json, .tick.lock) regardless of stored category. - Update test_cron_subtree_categorised to match fixed guess_category (only cron/output/* is cron-output, not all of cron/). Tests: add 5 regression tests in TestStaleCronEntryMigration.
Yuki-14544869
pushed a commit
to Yuki-14544869/hermes-agent
that referenced
this pull request
Jun 4, 2026
…sResearch#37721) quick() and dry_run() previously trusted the stored category from tracked.json without re-validating at delete time. Stale entries from before NousResearch#34840 could carry category="cron-output" for cron control-plane paths (e.g. cron/jobs.json), causing quick() to delete the live scheduler registry. Fix: - Fix guess_category() to only classify cron/output/** as cron-output (was classifying ALL cron/* paths, missing the NousResearch#34840 fix). - Re-validate cron-output entries via guess_category() at delete time in quick() and dry_run(); stale entries that are no longer classified as cron-output are skipped and removed from tracked.json. - Add _is_protected_cron_path() as a hard defense-in-depth guard that blocks deletion of cron/cronjobs directories and known control-plane files (jobs.json, .tick.lock) regardless of stored category. - Update test_cron_subtree_categorised to match fixed guess_category (only cron/output/* is cron-output, not all of cron/). Tests: add 5 regression tests in TestStaleCronEntryMigration.
davidgut1982
pushed a commit
to davidgut1982/hermes-agent
that referenced
this pull request
Jun 5, 2026
…sResearch#37721) quick() and dry_run() previously trusted the stored category from tracked.json without re-validating at delete time. Stale entries from before NousResearch#34840 could carry category="cron-output" for cron control-plane paths (e.g. cron/jobs.json), causing quick() to delete the live scheduler registry. Fix: - Fix guess_category() to only classify cron/output/** as cron-output (was classifying ALL cron/* paths, missing the NousResearch#34840 fix). - Re-validate cron-output entries via guess_category() at delete time in quick() and dry_run(); stale entries that are no longer classified as cron-output are skipped and removed from tracked.json. - Add _is_protected_cron_path() as a hard defense-in-depth guard that blocks deletion of cron/cronjobs directories and known control-plane files (jobs.json, .tick.lock) regardless of stored category. - Update test_cron_subtree_categorised to match fixed guess_category (only cron/output/* is cron-output, not all of cron/). Tests: add 5 regression tests in TestStaleCronEntryMigration.
changman
pushed a commit
to changman/hermes-agent
that referenced
this pull request
Jun 10, 2026
…sResearch#37721) quick() and dry_run() previously trusted the stored category from tracked.json without re-validating at delete time. Stale entries from before NousResearch#34840 could carry category="cron-output" for cron control-plane paths (e.g. cron/jobs.json), causing quick() to delete the live scheduler registry. Fix: - Fix guess_category() to only classify cron/output/** as cron-output (was classifying ALL cron/* paths, missing the NousResearch#34840 fix). - Re-validate cron-output entries via guess_category() at delete time in quick() and dry_run(); stale entries that are no longer classified as cron-output are skipped and removed from tracked.json. - Add _is_protected_cron_path() as a hard defense-in-depth guard that blocks deletion of cron/cronjobs directories and known control-plane files (jobs.json, .tick.lock) regardless of stored category. - Update test_cron_subtree_categorised to match fixed guess_category (only cron/output/* is cron-output, not all of cron/). Tests: add 5 regression tests in TestStaleCronEntryMigration.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cron jobs no longer silently vanish after
hermes update. Two independent failure paths are closed: the disk-cleanup plugin that was deleting the live registry (root cause), and a missing recovery net during update (defense-in-depth).Root cause (issue #32164)
plugins/disk-cleanup/disk_cleanup.py::guess_category()classified the entirecron/tree as disposablecron-output:Every cron run rewrites
cron/jobs.json(to updatenext_run_at). The disk-cleanup hook tracked that write ascron-output, and its auto-cleanup pass deletescron-outputafter 14 days — wiping the live scheduler registry. Hermes then reads the missing file as 0 jobs. (The original #34600 report guessed config migration, butmigrate_config()never opensjobs.json— disk-cleanup is the actual emptier.)Changes
plugins/disk-cleanup/disk_cleanup.py: onlycron/output/**is eligible for cleanup; top-level control-plane state (jobs.json,.tick.lock) is never auto-tracked. (fix(cron): exclude jobs.json registry from disk-cleanup pattern #33834, @sweetcornna)hermes_cli/backup.py+hermes_cli/main.py:restore_cron_jobs_if_emptied()— after update, ifjobs.jsonis now valid-but-empty while the pre-update snapshot held jobs, restore from the snapshot and warn. Conservative: only fires on unambiguous loss, never raises, leaves corrupt/unreadable files alone. (fix(cron): restore jobs.json emptied by config migration on update #34602, @Bartok9)scripts/release.py: AUTHOR_MAP entry for @sweetcornna.Validation
tests/plugins/test_disk_cleanup_plugin.pytests/hermes_cli/test_backup.pyjobs.json/.tick.lockprotected,cron/output/still tracked, snapshot restore fires on loss, no second-guessing genuine clears, corrupt files untouchedCloses #34600. Closes #32164.
Supersedes #34602, #33834, #32478, #32436, #30208 (credit preserved via cherry-pick).
Co-authored-by: sweetcornna 96944678+ymylive@users.noreply.github.com
Co-authored-by: Bartok9 danielrpike9@gmail.com
Infographic