Skip to content

fix(plugins): preserve cron registry from disk-cleanup auto-categorisation#32177

Closed
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/disk-cleanup-preserve-cron-registry-32164
Closed

fix(plugins): preserve cron registry from disk-cleanup auto-categorisation#32177
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/disk-cleanup-preserve-cron-registry-32164

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

What does this PR do?

plugins/disk-cleanup's guess_category() classified every top-level path under HERMES_HOME/cron/ (and the legacy cronjobs/ alias) as cron-output. That is correct for the per-job run artifacts under cron/output/<job>/, but it also catches durable scheduler state — cron/jobs.json (the live job registry) and cron/.tick.lock (the tick mutex). Once the post_tool_call hook auto-tracks jobs.json under cron-output, the next quick() pass deletes the registry; Hermes then interprets the missing file as an empty schedule and the user's entire cron list silently disappears.

This PR narrows the cron-output branch to paths inside cron/output/** (and cronjobs/output/** for the legacy alias); top-level control-plane files now fall through to None and are never auto-tracked. The fix is the one written into the issue body verbatim, with regression tests for the three durable paths the issue calls out.

Related Issue

Fixes #32164

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • plugins/disk-cleanup/disk_cleanup.pyguess_category() now requires rel.parts[1] == \"output\" before returning cron-output; top-level cron files fall through to None.
  • tests/plugins/test_disk_cleanup_plugin.py — reshape test_cron_subtree_categorised around the new cron/output/<job>/run.md layout; add test_cron_registry_not_classified, test_cron_tick_lock_not_classified, and test_cronjobs_registry_not_classified as regression guards.

How to Test

  1. Reproduce the bug on main: with the patch reverted, guess_category(Path('~/.hermes/cron/jobs.json')) returns 'cron-output', which means a single post_tool_call auto-track + quick cleanup pass (14 days later) deletes the registry.
  2. With the patch applied: guess_category(Path('~/.hermes/cron/jobs.json')) returns None; guess_category(Path('~/.hermes/cron/output/myjob/run.md')) still returns 'cron-output'.
  3. uv run --with pytest --with pytest-xdist --with pytest-asyncio python3 -m pytest tests/plugins/test_disk_cleanup_plugin.py -v → 41 passed locally.

Regression-guard pass: stashed plugins/disk-cleanup/disk_cleanup.py and re-ran the three new tests on the unpatched code — all three failed with 'cron-output' is None, confirming they exercise the bug.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run focused tests for the touched code and all pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15.x

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A (inline comment explains the carve-out; existing module docstring is still accurate)
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — the path-parts check uses Path.parts so the same logic applies on Windows
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Screenshots / Logs

Audited siblings: scanned the rest of guess_category() for top-level dict entries that classify durable state. The other catch-all branches (disk-cleanup, logs, memories, sessions, config.yaml, etc.) already return None. top == \"cache\" returns \"temp\" but cache contents are regeneratable by definition — no known durable registry lives directly under ~/.hermes/cache/. No additional widening is needed for this fix.

Copilot AI review requested due to automatic review settings May 25, 2026 17:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates disk-cleanup auto-tracking so cron scheduler control-plane files aren’t mistakenly classified as deletable “cron-output”, preventing accidental deletion of the job registry.

Changes:

  • Narrow guess_category()’s cron/cronjobs classification to only cron/output/**.
  • Add regression tests ensuring cron registries/locks are never categorized as cron-output.
  • Update the existing cron test to target a realistic cron/output/<job>/... subtree.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
tests/plugins/test_disk_cleanup_plugin.py Adds/adjusts tests for cron output vs durable cron registry files.
plugins/disk-cleanup/disk_cleanup.py Restricts cron categorization to cron/output/** to avoid deleting scheduler state.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

p = cron_dir / "job_output.md"
output_dir = _isolate_env / "cron" / "output" / "myjob"
output_dir.mkdir(parents=True)
p = output_dir / "run.md"
p.write_text("x")
assert dg.guess_category(p) == "cron-output"

Comment on lines +161 to +169
def test_cronjobs_registry_not_classified(self, _isolate_env):
# ``cronjobs`` is the legacy alias for the same top-level dir;
# the same registry-preservation invariant must hold.
dg = _load_lib()
cron_dir = _isolate_env / "cronjobs"
cron_dir.mkdir()
p = cron_dir / "jobs.json"
p.write_text("[]")
assert dg.guess_category(p) is None
Comment on lines +140 to +170
def test_cron_registry_not_classified(self, _isolate_env):
# Regression for #32164: the live scheduler registry must never
# be auto-tracked as ``cron-output`` or quick cleanup will delete
# it and Hermes will then read missing-registry as zero jobs.
dg = _load_lib()
cron_dir = _isolate_env / "cron"
cron_dir.mkdir()
p = cron_dir / "jobs.json"
p.write_text("[]")
assert dg.guess_category(p) is None

def test_cron_tick_lock_not_classified(self, _isolate_env):
# ``cron/.tick.lock`` is also durable control-plane state; same
# invariant as ``jobs.json``.
dg = _load_lib()
cron_dir = _isolate_env / "cron"
cron_dir.mkdir()
p = cron_dir / ".tick.lock"
p.write_text("")
assert dg.guess_category(p) is None

def test_cronjobs_registry_not_classified(self, _isolate_env):
# ``cronjobs`` is the legacy alias for the same top-level dir;
# the same registry-preservation invariant must hold.
dg = _load_lib()
cron_dir = _isolate_env / "cronjobs"
cron_dir.mkdir()
p = cron_dir / "jobs.json"
p.write_text("[]")
assert dg.guess_category(p) is None

@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/plugins Plugin system and bundled plugins comp/cron Cron scheduler and job management labels May 25, 2026
…ation

`guess_category()` classified every top-level path under
`HERMES_HOME/cron/` (and the legacy `cronjobs/` alias) as `cron-output`,
which makes the file eligible for unconditional deletion after 14 days
in `quick()`. That is correct for run artifacts under
`cron/output/<job>/` but it also catches durable scheduler state at the
top of the dir:

  - `HERMES_HOME/cron/jobs.json`   — the scheduler's job registry
  - `HERMES_HOME/cron/.tick.lock`  — the scheduler's tick mutex

When `disk-cleanup`'s `post_tool_call` hook auto-tracks one of those
paths (any tool that touches it through the safe-path filter qualifies)
the next quick cleanup pass deletes the registry; Hermes then loads the
missing file as an empty job list and the user's entire cron schedule
silently disappears.

Restrict `cron-output` classification to paths under
`cron/output/**` (and `cronjobs/output/**`); top-level cron control
files now correctly fall through to `None` and are never auto-tracked.

Regression tests cover the three known durable paths plus the legacy
`cronjobs/` alias. The existing happy-path test is reshaped around the
new `cron/output/<job>/` layout to keep coverage on the disposable
side.

Fixes NousResearch#32164.
@briandevans briandevans force-pushed the fix/disk-cleanup-preserve-cron-registry-32164 branch from bd1d47f to 4207fe1 Compare May 28, 2026 07:09
@briandevans

Copy link
Copy Markdown
Contributor Author

Closing — the cron-registry preservation this PR adds is already on main: guess_category() now restricts cron output to cron/output/** and protects jobs.json/.tick.lock (citing #32164), with equivalent regression tests already present. The change here is now a no-op against main. Happy to reopen if a gap remains.

@briandevans briandevans closed this Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management comp/plugins Plugin system and bundled plugins P1 High — major feature broken, no workaround type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

disk-cleanup misclassifies cron/jobs.json as cron-output and can delete the live cron registry

3 participants