fix(backup): floor pre-update backup_keep to 1 so the new backup survives#16555
Closed
briandevans wants to merge 1 commit into
Closed
fix(backup): floor pre-update backup_keep to 1 so the new backup survives#16555briandevans wants to merge 1 commit into
briandevans wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a retention edge case in the pre-update auto-backup rotation so misconfigured updates.backup_keep values (0/negative) can’t immediately prune the just-created backup, and adds regression coverage + config clarification to prevent recurrence.
Changes:
- Floor
_prune_pre_update_backups(..., keep)to a minimum of 1 to ensure the newly created pre-update zip is preserved. - Add three regression tests covering
keep=0,keep<0, and verifying older backups are still pruned under the floor. - Clarify in
DEFAULT_CONFIGcomments that values below 1 are floored and how to disable backups properly.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
hermes_cli/backup.py |
Floors keep to >= 1 in _prune_pre_update_backups and documents the rationale to prevent pruning the freshly created backup. |
tests/hermes_cli/test_backup.py |
Adds targeted regression tests to ensure keep=0/negative preserves the new backup while still rotating older zips. |
hermes_cli/config.py |
Updates the config comment to document the floor-to-1 behavior and the correct way to disable pre-update backups. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
3 tasks
…ives
`updates.backup_keep: 0` (or any negative value) wiped the freshly-
created pre-update zip:
_prune_pre_update_backups(backup_dir, keep=0):
backups = sorted(..., reverse=True) # newest first, includes
# the zip we just wrote
for p in backups[0:]: # = all of them
p.unlink()
The wrapper in `main.py` then printed `Saved: <path>` for a file that
no longer existed (the size lookup is wrapped in `try/except OSError`
which silently degrades to "0 B"), leaving operators believing they had
a recovery point when they had none.
This is a real footgun because some config systems treat 0 as "keep
unlimited"; here it does the opposite — every backup is destroyed
right after creation.
Fix: clamp `keep` to a minimum of 1 inside `_prune_pre_update_backups`
since that helper is only invoked immediately after a fresh backup
is written. Operators who genuinely want no backups should set
`updates.pre_update_backup: false` (which gates creation entirely)
rather than relying on `backup_keep: 0`.
Also extends the `backup_keep` config docstring to spell out the floor
and point at `pre_update_backup: false` as the off-switch.
## Tests
Three regression tests added in `TestPreUpdateBackup`:
- `test_keep_zero_does_not_delete_freshly_created_backup` —
asserts the file persists after `keep=0`
- `test_keep_negative_does_not_delete_freshly_created_backup` —
same for negative values
- `test_keep_zero_still_prunes_older_backups` — proves the floor
only protects the new backup; older ones are still rotated out
Verified the new tests fail on origin/main (without the floor) and
pass with it; full `tests/hermes_cli/test_backup.py` suite green
(84 tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
c274380 to
f14ffa5
Compare
Contributor
|
Salvaged via #19730 onto current main - your commit authorship was preserved. Thanks! |
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
updates.backup_keep: 0(or negative) silently deletes the freshly-created pre-update backup, leaving the user with no recovery point even thoughSaved: <path>was printed.keepparameter to a minimum of 1 inside_prune_pre_update_backupsso the just-written zip is always preserved.The bug
_prune_pre_update_backups(backup_dir, keep)only clampskeep < 0up to 0:The function is called from
create_pre_update_backup()immediately after the new zip is written. So withkeep=0:<HERMES_HOME>/backups/pre-update-<ts>.zip._prune_pre_update_backups(..., keep=0)lists everypre-update-*.zip(one entry — the new one) and deletes it.main.pycallsout_path.stat().st_size, which raisesOSError(file missing) — caught and silently degraded tosize_bytes = 0.Saved: ~/.hermes/backups/pre-update-….zip (0 B, 1.2s)and believes they have a recovery point.This is exactly the kind of footgun config systems trigger because some apps treat
0as "unlimited" / "no rotation". Here it does the opposite — every backup is destroyed right after creation, with no warning.Reproduced locally:
The fix
Clamp
keepto a minimum of 1 inside_prune_pre_update_backups:The pruner is only ever invoked immediately after a fresh backup is written, so the smallest sensible retention is 1 — anything below that means "throw away the file we just paid disk + CPU to produce." Operators who genuinely want no backups should set
updates.pre_update_backup: false(which gates creation entirely) rather than relying onbackup_keep: 0. The config docstring is updated to spell this out.The floor only protects the just-created backup; older zips are still rotated out as before.
test_keep_zero_still_prunes_older_backupsproves this.Test plan
8ed599dc0, the merge commit of feat(update): auto-backup HERMES_HOME before hermes update #16539): the three new tests fail withkeep=0deleting the freshly-created backup.TestPreUpdateBackuptests pass; fulltests/hermes_cli/test_backup.pysuite green (84 tests).hermes_cli/backup.py→ 3 new tests fail with "keep=0 silently deleted the freshly-created backup" / "Floor must preserve the new backup". Restored → all pass.keep=5) is unchanged.Tests added (parametrized in spirit by covering both 0 and negative):
test_keep_zero_does_not_delete_freshly_created_backuptest_keep_negative_does_not_delete_freshly_created_backuptest_keep_zero_still_prunes_older_backups— proves the floor doesn't regress rotation for older filesRelated
feat(update): auto-backup HERMES_HOME before hermes update) — the new auto-backup feature merged earlier today (8ed599d).gh search prs ... pre_update_backup / backup_keep / _prune_pre_update_backups / create_pre_update_backupall clean as of 2026-04-27 13:30Z).🤖 Generated with Claude Code