agent/file_safety.py: is_write_denied() returns a bare bool for four very different denial reasons:
- exact deny-list hit (
~/.ssh/id_ed25519, .env, /etc/shadow, …),
- deny-prefix hit (
~/.ssh/, ~/.aws/, /etc/systemd/, …),
mcp-tokens / pairing dirs under a Hermes home,
- path merely outside the
HERMES_WRITE_SAFE_ROOT allowlist.
Both call sites in tools/file_operations.py (write_file ~L1387, patch_file ~L1570 on current main) then emit the same message for all four:
Write denied: '<path>' is a protected system/credential file.
For case 4 this is simply false, and it misleads both the model and the operator. Observed on our deployment (v2026.7.7.2 / app 0.18.2, Docker, HERMES_WRITE_SAFE_ROOT=/opt/data): the agent tried to stage a throwaway helper at /tmp/trilium_report.py, was told /tmp/... "is a protected system/credential file", concluded /tmp was a sensitive path, and burned a retry cycle rediscovering by trial that /opt/data works. The end-of-turn file-mutation verifier then (correctly) flagged the failed write, which read to the operator like a credential incident — for what was only an allowlist miss.
Suggestion: return/propagate a denial reason and split the message, e.g.
- cases 1–3:
Write denied: '<path>' is a protected system/credential file.
- case 4:
Write denied: '<path>' is outside the allowed write roots (HERMES_WRITE_SAFE_ROOT=/opt/data).
Naming the allowed root in the error is safe (it is the agent's own working directory, already visible to it) and turns a dead-end error into a self-correcting one — the model's next attempt lands in the right place without a probe loop.
agent/file_safety.py: is_write_denied()returns a bare bool for four very different denial reasons:~/.ssh/id_ed25519,.env,/etc/shadow, …),~/.ssh/,~/.aws/,/etc/systemd/, …),mcp-tokens/pairingdirs under a Hermes home,HERMES_WRITE_SAFE_ROOTallowlist.Both call sites in
tools/file_operations.py(write_file~L1387,patch_file~L1570 on current main) then emit the same message for all four:For case 4 this is simply false, and it misleads both the model and the operator. Observed on our deployment (v2026.7.7.2 / app 0.18.2, Docker,
HERMES_WRITE_SAFE_ROOT=/opt/data): the agent tried to stage a throwaway helper at/tmp/trilium_report.py, was told/tmp/..."is a protected system/credential file", concluded/tmpwas a sensitive path, and burned a retry cycle rediscovering by trial that/opt/dataworks. The end-of-turn file-mutation verifier then (correctly) flagged the failed write, which read to the operator like a credential incident — for what was only an allowlist miss.Suggestion: return/propagate a denial reason and split the message, e.g.
Write denied: '<path>' is a protected system/credential file.Write denied: '<path>' is outside the allowed write roots (HERMES_WRITE_SAFE_ROOT=/opt/data).Naming the allowed root in the error is safe (it is the agent's own working directory, already visible to it) and turns a dead-end error into a self-correcting one — the model's next attempt lands in the right place without a probe loop.