fix: preserve symlinks during atomic file writes (#16743)

os.replace(tmp, path) replaces the symlink itself with a regular file,
breaking users who symlink config.yaml, SOUL.md, or .env from ~/.hermes/
to a dotfiles repo or managed profile package.

Fix: resolve symlinks via os.path.realpath() before os.replace(), so the
real file is overwritten in-place while the symlink survives.

Fixed in 7 files covering all os.replace call sites:
- utils.py (atomic_json_write, atomic_yaml_write — fixes save_config)
- hermes_cli/config.py (env sanitizer, save_env_value, remove_env_value)
- tools/skill_manager_tool.py (_atomic_write_text — SOUL.md writes)
- tools/memory_tool.py (memory file writes)
- tools/skills_sync.py (manifest writes)
- cron/jobs.py (job state + output file writes)
- agent/shell_hooks.py (hook file writes)

Fixes NousResearch/hermes-agent#16743
This commit is contained in:
vominh1919 2026-04-28 09:34:55 +07:00 committed by Teknium
parent 1369dae226
commit 3ab97a32d1
7 changed files with 38 additions and 13 deletions

View file

@ -448,7 +448,10 @@ class MemoryStore:
f.write(content)
f.flush()
os.fsync(f.fileno())
os.replace(tmp_path, str(path)) # Atomic on same filesystem
# Resolve symlinks so os.replace writes to the real file (GitHub #16743).
path_str = str(path)
real_path = os.path.realpath(path_str) if os.path.islink(path_str) else path_str
os.replace(tmp_path, real_path)
except BaseException:
# Clean up temp file on any failure
try: