mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(profiles): preserve symlinks during profile export
shutil.copytree() defaults to symlinks=False which follows symlinks and crashes on broken ones. In Docker/custom HERMES_HOME deployments, unrelated directories may contain stale symlinks that break export. Add symlinks=True to both copytree() calls in export_profile() so broken symlinks are preserved as symlink entries in the archive. Fixes #58394
This commit is contained in:
parent
9ae17b8ac5
commit
b6b9bcd2a1
2 changed files with 29 additions and 0 deletions
|
|
@ -1890,6 +1890,7 @@ def export_profile(name: str, output_path: str) -> Path:
|
|||
shutil.copytree(
|
||||
profile_dir,
|
||||
staged,
|
||||
symlinks=True,
|
||||
ignore=_default_export_ignore(profile_dir),
|
||||
)
|
||||
result = shutil.make_archive(base, "gztar", tmpdir, "default")
|
||||
|
|
@ -1902,6 +1903,7 @@ def export_profile(name: str, output_path: str) -> Path:
|
|||
shutil.copytree(
|
||||
profile_dir,
|
||||
staged,
|
||||
symlinks=True,
|
||||
ignore=lambda d, contents: _CREDENTIAL_FILES & set(contents),
|
||||
)
|
||||
result = shutil.make_archive(base, "gztar", tmpdir, canon)
|
||||
|
|
|
|||
|
|
@ -1385,6 +1385,33 @@ class TestExportImport:
|
|||
|
||||
assert not any("__pycache__" in n for n in names)
|
||||
|
||||
def test_export_default_handles_broken_symlinks(self, profile_env, tmp_path):
|
||||
"""Export succeeds when the profile directory contains broken symlinks.
|
||||
|
||||
In Docker/custom HERMES_HOME deployments, unrelated directories may
|
||||
contain stale symlinks. copytree must not follow them.
|
||||
"""
|
||||
default_dir = get_profile_dir("default")
|
||||
(default_dir / "config.yaml").write_text("ok")
|
||||
# Create a broken symlink (target does not exist)
|
||||
(default_dir / "broken_link").symlink_to("/nonexistent/path")
|
||||
# Create a valid symlink for comparison
|
||||
(default_dir / "valid_target.txt").write_text("real data")
|
||||
(default_dir / "valid_link").symlink_to(default_dir / "valid_target.txt")
|
||||
|
||||
output = tmp_path / "export" / "default.tar.gz"
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
result = export_profile("default", str(output))
|
||||
|
||||
assert result.exists()
|
||||
with tarfile.open(str(result), "r:gz") as tf:
|
||||
names = tf.getnames()
|
||||
# Broken symlink is preserved as a symlink entry
|
||||
assert any("broken_link" in n for n in names)
|
||||
# Valid symlink and its target are both present
|
||||
assert any("valid_link" in n for n in names)
|
||||
assert any("valid_target.txt" in n for n in names)
|
||||
|
||||
def test_import_default_without_name_raises(self, profile_env, tmp_path):
|
||||
"""Importing a default export without --name gives clear guidance."""
|
||||
default_dir = get_profile_dir("default")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue