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.
This commit is contained in:
Alessandro 2026-05-26 14:54:53 +02:00
parent 22e79811f3
commit 369e0df17f
2 changed files with 75 additions and 1 deletions

View file

@ -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()

View file

@ -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 = []