From 369e0df17f60463d7619963a2fd49420b896afda Mon Sep 17 00:00:00 2001 From: Alessandro <155005371+3clyp50@users.noreply.github.com> Date: Tue, 26 May 2026 14:54:53 +0200 Subject: [PATCH] Skip transient Desktop SSH agent state during self-update backup Exclude the Desktop profile .ssh/agent runtime directory from usr backups during self-update so live SSH agent sockets do not abort upgrades. Keep the rule in the self-update manager, where the usr backup actually runs, and cover it with a regression test alongside the existing runtime-socket backup cases. --- docker/run/fs/exe/self_update_manager.py | 29 ++++++++++++++- tests/test_self_update_tag_filter.py | 47 ++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/docker/run/fs/exe/self_update_manager.py b/docker/run/fs/exe/self_update_manager.py index 63f751b07..a1917bc31 100644 --- a/docker/run/fs/exe/self_update_manager.py +++ b/docker/run/fs/exe/self_update_manager.py @@ -363,8 +363,17 @@ def create_usr_backup( compression=zipfile.ZIP_DEFLATED, compresslevel=6, ) as archive: - for root, _, files in os.walk(usr_dir): + for root, dirs, files in os.walk(usr_dir): root_path = Path(root) + root_relative = root_path.relative_to(usr_dir) + dirs[:] = [ + dirname + for dirname in dirs + if not should_exclude_from_usr_backup( + root_relative / dirname, + logger, + ) + ] for filename in files: source_file = root_path / filename if not should_include_usr_backup_entry(source_file, logger): @@ -386,6 +395,24 @@ def create_usr_backup( temporary_backup.unlink(missing_ok=True) +def should_exclude_from_usr_backup( + relative_dir: Path, + logger: AttemptLogger, +) -> bool: + parts = relative_dir.parts + if ( + len(parts) >= 6 + and parts[0] == "plugins" + and parts[1] == "_desktop" + and parts[2] == "profiles" + and parts[-2] == ".ssh" + and parts[-1] == "agent" + ): + logger.log(f"Skipping transient usr backup directory: {Path('usr') / relative_dir}") + return True + return False + + def should_include_usr_backup_entry(source_file: Path, logger: AttemptLogger) -> bool: try: source_stat = source_file.lstat() diff --git a/tests/test_self_update_tag_filter.py b/tests/test_self_update_tag_filter.py index 346217929..056225659 100644 --- a/tests/test_self_update_tag_filter.py +++ b/tests/test_self_update_tag_filter.py @@ -829,6 +829,53 @@ def test_self_update_manager_usr_backup_skips_runtime_sockets(): ) +def test_self_update_manager_usr_backup_skips_transient_desktop_ssh_agent_dir(tmp_path): + manager = load_self_update_manager() + repo_dir = tmp_path / "repo" + usr_dir = repo_dir / "usr" + ssh_dir = ( + usr_dir + / "plugins" + / "_desktop" + / "profiles" + / "agent-zero-desktop" + / ".ssh" + ) + transient_agent_dir = ssh_dir / "agent" + transient_agent_dir.mkdir(parents=True) + (transient_agent_dir / "socket").write_text("ephemeral\n", encoding="utf-8") + (ssh_dir / "config").write_text("Host github.com\n", encoding="utf-8") + (usr_dir / "settings.json").write_text('{"ok": true}\n', encoding="utf-8") + messages = [] + + class ListLogger: + def log(self, message=""): + messages.append(message) + + backup_path = manager.create_usr_backup( + repo_dir=repo_dir, + backup_path=str(tmp_path / "backups"), + backup_name="usr-backup.zip", + conflict_policy="rename", + logger=ListLogger(), + ) + + with zipfile.ZipFile(backup_path) as archive: + names = set(archive.namelist()) + + assert "usr/settings.json" in names + assert "usr/plugins/_desktop/profiles/agent-zero-desktop/.ssh/config" in names + assert ( + "usr/plugins/_desktop/profiles/agent-zero-desktop/.ssh/agent/socket" + not in names + ) + assert any( + "Skipping transient usr backup directory: " + "usr/plugins/_desktop/profiles/agent-zero-desktop/.ssh/agent" in message + for message in messages + ) + + def test_self_update_manager_clean_uv_cache_uses_uv_when_available(monkeypatch): manager = load_self_update_manager() commands = []