fix(profiles): exclude session history, backups, and snapshots from --clone-all (#45246)

--clone-all copied the source profile's state.db, sessions/, backups/,
state-snapshots/, and checkpoints/ into the new profile. These are
per-profile history: a 49GB copy in practice (15GB snapshots + 11GB
backup archives + 16GB state.db + 6.4GB sessions), and restoring a
copied backup inside the clone would resurrect the SOURCE profile's
state. A clone is a fresh workspace; history stays with the source.

New _CLONE_ALL_HISTORY_EXCLUDE_ROOT set, applied at root level for ANY
source profile (named profiles accumulate the same artifacts), unlike
the default-gated infrastructure excludes. Nested same-name dirs still
copy. Docs and the post-create CLI message updated to match; profile
export / hermes backup remain the full-history paths.
This commit is contained in:
Teknium
2026-06-12 15:41:50 -07:00
committed by GitHub
parent 0db5cb8e75
commit 7a318aae22
5 changed files with 89 additions and 27 deletions
+35 -7
View File
@@ -268,9 +268,9 @@ class TestCreateProfile:
def test_clone_all_excludes_default_infrastructure(self, profile_env):
"""--clone-all from default profile excludes hermes-agent, .worktrees,
bin, node_modules at root, plus __pycache__/*.pyc/*.pyo/*.sock/*.tmp
at any depth. Profile data (config, env, skills, sessions, logs,
state.db) must be preserved — clone-all means "complete snapshot
minus infrastructure."
at any depth. Profile data (config, env, skills, logs) must be
preserved — clone-all means "complete snapshot minus infrastructure
and per-profile history."
"""
tmp_path = profile_env
default_home = tmp_path / ".hermes"
@@ -296,8 +296,6 @@ class TestCreateProfile:
(default_home / "skills" / "my-skill" / "SKILL.md").write_text("skill")
(default_home / "config.yaml").write_text("model: gpt-4")
(default_home / ".env").write_text("KEY=val")
(default_home / "state.db").write_text("sessions-data")
(default_home / "sessions").mkdir(exist_ok=True)
(default_home / "logs").mkdir(exist_ok=True)
(default_home / "logs" / "gateway.log").write_text("log")
@@ -319,10 +317,40 @@ class TestCreateProfile:
assert (profile_dir / "skills" / "my-skill" / "SKILL.md").read_text() == "skill"
assert (profile_dir / "config.yaml").read_text() == "model: gpt-4"
assert (profile_dir / ".env").read_text() == "KEY=val"
assert (profile_dir / "state.db").read_text() == "sessions-data"
assert (profile_dir / "sessions").exists()
assert (profile_dir / "logs" / "gateway.log").read_text() == "log"
def test_clone_all_excludes_history_artifacts(self, profile_env):
"""--clone-all excludes the source's session history, backups, and
snapshots — a clone is a fresh workspace, and these can reach tens
of GB. Applies to ANY source profile, not just default.
"""
tmp_path = profile_env
default_home = tmp_path / ".hermes"
(default_home / "state.db").write_text("sessions-data")
(default_home / "state.db-wal").write_text("wal")
(default_home / "state.db-shm").write_text("shm")
(default_home / "sessions" / "20260101_old").mkdir(parents=True)
(default_home / "backups").mkdir(exist_ok=True)
(default_home / "backups" / "backup.tar.gz").write_text("archive")
(default_home / "state-snapshots" / "snap1").mkdir(parents=True)
(default_home / "checkpoints" / "cp1").mkdir(parents=True)
# Data that should still copy
(default_home / "config.yaml").write_text("model: gpt-4")
# Nested dirs with the same names must NOT be excluded (root-only)
(default_home / "workspace" / "backups").mkdir(parents=True)
(default_home / "workspace" / "backups" / "user-data.txt").write_text("mine")
profile_dir = create_profile("fresh", clone_all=True, no_alias=True)
for history in (
"state.db", "state.db-wal", "state.db-shm",
"sessions", "backups", "state-snapshots", "checkpoints",
):
assert not (profile_dir / history).exists(), history
assert (profile_dir / "config.yaml").read_text() == "model: gpt-4"
# Root-only: nested same-name dirs survive
assert (profile_dir / "workspace" / "backups" / "user-data.txt").read_text() == "mine"
def test_clone_config_missing_files_skipped(self, profile_env):
"""Clone config gracefully skips files that don't exist in source."""
profile_dir = create_profile("coder", clone_config=True, no_alias=True)