fix(utils): guard os.fchmod for Windows in atomic_json_write

os.fchmod is Unix-only; the Windows os module has no fchmod (only
chmod). Passing mode= (e.g. 0o600 when saving the Hindsight config
during `hermes memory setup`) crashed on Windows with:

    AttributeError: module 'os' has no attribute 'fchmod'

Guard the fchmod fast-path with hasattr(os, "fchmod"). Skipping it on
Windows is safe: mkstemp already creates the temp file as 0o600, and
the existing post-replace os.chmod(real_path, mode) — already wrapped
in try/except — applies the final mode durably (as far as Windows
honors it).

Adds regression tests: one simulating a Windows os module without
fchmod (must not raise), and one asserting the durable 0o600 mode on
POSIX.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben
2026-06-01 10:20:19 -04:00
committed by kshitij
parent a5371b3e68
commit b9646276fd
2 changed files with 37 additions and 1 deletions

View File

@ -117,7 +117,10 @@ def atomic_json_write(
suffix=".tmp",
)
try:
if mode is not None:
if mode is not None and hasattr(os, "fchmod"):
# fchmod is Unix-only; Windows' os module has no fchmod. Skipping it
# here is safe — mkstemp already created the temp file as 0o600, and
# the post-replace os.chmod below applies the final mode durably.
os.fchmod(fd, mode)
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(