fix(backup): handle files with pre-1980 timestamps

ZipFile.write() raises ValueError for files with mtime before 1980-01-01
(the ZIP format uses MS-DOS timestamps which can't represent earlier dates).
This crashes the entire backup. Add ValueError to the existing except clause
so these files are skipped and reported in the warnings summary, matching the
existing behavior for PermissionError and OSError.
This commit is contained in:
salt-555 2026-04-13 19:24:48 -06:00 committed by Teknium
parent afba54364e
commit 12c8cefbce
2 changed files with 29 additions and 1 deletions

View file

@ -201,7 +201,7 @@ def run_backup(args) -> None:
else: else:
zf.write(abs_path, arcname=str(rel_path)) zf.write(abs_path, arcname=str(rel_path))
total_bytes += abs_path.stat().st_size total_bytes += abs_path.stat().st_size
except (PermissionError, OSError) as exc: except (PermissionError, OSError, ValueError) as exc:
errors.append(f" {rel_path}: {exc}") errors.append(f" {rel_path}: {exc}")
continue continue

View file

@ -702,6 +702,34 @@ class TestBackupEdgeCases:
# Zip should still be created with the readable files # Zip should still be created with the readable files
assert out_zip.exists() assert out_zip.exists()
def test_pre1980_timestamp_skipped(self, tmp_path, monkeypatch):
"""Backup skips files with pre-1980 timestamps (ZIP limitation)."""
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
(hermes_home / "config.yaml").write_text("model: test\n")
# Create a file with epoch timestamp (1970-01-01)
old_file = hermes_home / "ancient.txt"
old_file.write_text("old data")
os.utime(old_file, (0, 0))
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
monkeypatch.setattr(Path, "home", lambda: tmp_path)
out_zip = tmp_path / "out.zip"
args = Namespace(output=str(out_zip))
from hermes_cli.backup import run_backup
run_backup(args)
# Zip should still be created with the valid files
assert out_zip.exists()
with zipfile.ZipFile(out_zip, "r") as zf:
names = zf.namelist()
assert "config.yaml" in names
# The pre-1980 file should be skipped, not crash the backup
assert "ancient.txt" not in names
def test_skips_output_zip_inside_hermes(self, tmp_path, monkeypatch): def test_skips_output_zip_inside_hermes(self, tmp_path, monkeypatch):
"""Backup skips its own output zip if it's inside hermes root.""" """Backup skips its own output zip if it's inside hermes root."""
hermes_home = tmp_path / ".hermes" hermes_home = tmp_path / ".hermes"