From 208f0d7c3bbb6c63cdf5e2d82d4d4b7cb324b00d Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:01:09 -0700 Subject: [PATCH] fix(update): default pre-update backup to off (#52729) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- hermes_cli/config.py | 16 ++++++++-------- hermes_cli/main.py | 13 ++++++------- hermes_cli/subcommands/update.py | 2 +- tests/hermes_cli/test_backup.py | 19 ++++++++----------- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 2d1985cabf0..ca79a849c39 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -2784,14 +2784,14 @@ DEFAULT_CONFIG = { "updates": { # Run a full ``hermes backup``-style zip of HERMES_HOME before every # ``hermes update``. Backups land in ``/backups/`` and - # can be restored with ``hermes import ``. 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 ``. 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 diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 59e0ab93dc1..b4176f13e16 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -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: diff --git a/hermes_cli/subcommands/update.py b/hermes_cli/subcommands/update.py index ddfe1db30a1..b2a632f202c 100644 --- a/hermes_cli/subcommands/update.py +++ b/hermes_cli/subcommands/update.py @@ -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", diff --git a/tests/hermes_cli/test_backup.py b/tests/hermes_cli/test_backup.py index c576b726d7a..cb55fec50ac 100644 --- a/tests/hermes_cli/test_backup.py +++ b/tests/hermes_cli/test_backup.py @@ -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