fix(update): default pre-update backup to off (#52729)

The pre-update HERMES_HOME zip shipped on by default (DEFAULT_CONFIG +
runtime fallback both True), so every `hermes update` zipped the entire
~/.hermes — sessions DB, caches, skills — adding minutes to each update.
The shipped cli-config.yaml.example, the --backup help, and the example
config all already said "off by default," so the live default
contradicted its own documentation.

Flip the default to off everywhere: DEFAULT_CONFIG, the runtime
`.get(..., False)` fallback in _run_pre_update_backup, and the stale
--backup help string. Users who want the #48200 safety net opt in via
updates.pre_update_backup: true or --backup for a single run.

Updated test_default_enabled_creates_backup -> test_default_disabled_is_silent
to assert the new default (silent no-op, no zip).
This commit is contained in:
Teknium 2026-06-25 16:01:09 -07:00 committed by GitHub
parent e4ff494860
commit 208f0d7c3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 23 additions and 27 deletions

View file

@ -2784,14 +2784,14 @@ DEFAULT_CONFIG = {
"updates": {
# Run a full ``hermes backup``-style zip of HERMES_HOME before every
# ``hermes update``. Backups land in ``<HERMES_HOME>/backups/`` and
# can be restored with ``hermes import <path>``. Defaults to true
# after the #48200 incident: a ``hermes update --yes`` run that
# computed a wrong path silently wiped the user's ``.env``,
# ``MEMORY.md``, ``kanban.db``, custom skills, and scripts in one
# go. The cost of a few minutes of zip time per update is
# negligible compared to the alternative. Set to false to opt
# out, or pass ``--no-backup`` for a single update run.
"pre_update_backup": True,
# can be restored with ``hermes import <path>``. Off by default:
# zipping a large HERMES_HOME (sessions DB, caches, skills) can add
# minutes to every update. The #48200 incident — a ``hermes update
# --yes`` run that computed a wrong path and silently wiped the
# user's ``.env``, ``MEMORY.md``, ``kanban.db``, custom skills, and
# scripts — is the reason this knob exists; enable it (here, or via
# ``--backup`` for a single run) if you want that safety net.
"pre_update_backup": False,
# How many pre-update backup zips to retain. Older ones are pruned
# automatically after each successful backup. Values below 1 are
# floored to 1 — the backup just created is always preserved. To

View file

@ -8324,13 +8324,12 @@ def _run_pre_update_backup(args) -> None:
cfg = {}
updates_cfg = cfg.get("updates", {}) if isinstance(cfg, dict) else {}
# The default config ships with ``pre_update_backup: true`` (see
# ``hermes_cli/config.py``). Fall back to true if the key is missing
# (e.g. a user has an older custom config without the field). The
# ``False`` default from before #48200 caused silent data loss when
# an update step computed a wrong path — the cost of a few minutes
# of zip time per update is negligible compared to the alternative.
enabled = updates_cfg.get("pre_update_backup", True)
# The default config ships with ``pre_update_backup: false`` (see
# ``hermes_cli/config.py``). Fall back to false if the key is missing
# so the default behaviour matches the shipped config: zipping a large
# HERMES_HOME can add minutes to every update. Users who want the
# #48200 safety net opt in via the config knob or ``--backup``.
enabled = updates_cfg.get("pre_update_backup", False)
keep = updates_cfg.get("backup_keep", 5)
if not enabled and not force_backup:

View file

@ -41,7 +41,7 @@ def build_update_parser(subparsers, *, cmd_update: Callable) -> None:
"--backup",
action="store_true",
default=False,
help="Force a pre-update backup for this run (off by default; overrides updates.pre_update_backup)",
help="Force a pre-update backup for this run (off by default; overrides updates.pre_update_backup=false)",
)
update_parser.add_argument(
"--yes",

View file

@ -1863,21 +1863,18 @@ class TestRunPreUpdateBackup:
backups = list((hermes_home / "backups").glob("pre-update-*.zip"))
assert len(backups) == 1
def test_default_enabled_creates_backup(self, hermes_home, capsys):
"""With the new safe default (``pre_update_backup: true``), every
``hermes update`` creates a backup before any destructive step
runs the cost is a few minutes of zip time vs. the alternative
of silent total data loss of ``~/.hermes/`` observed in #48200
when an update step computes a wrong path and the user had no
safety net.
def test_default_disabled_is_silent(self, hermes_home, capsys):
"""With the default (``pre_update_backup: false``), ``hermes update``
does NOT create a backup and stays silent zipping a large
HERMES_HOME can add minutes to every update. Users who want the
#48200 safety net opt in via the config knob or ``--backup``.
"""
from hermes_cli.main import _run_pre_update_backup
_run_pre_update_backup(Namespace(no_backup=False, backup=False))
out = capsys.readouterr().out
assert "Creating pre-update backup" in out
assert "Saved:" in out
backups = list((hermes_home / "backups").glob("pre-update-*.zip"))
assert len(backups) == 1
assert out == ""
assert not list((hermes_home / "backups").glob("pre-update-*.zip")) \
if (hermes_home / "backups").exists() else True
def test_no_backup_flag_skips(self, hermes_home, capsys):
from hermes_cli.main import _run_pre_update_backup