Skip to content

Write-guard error message conflates "outside HERMES_WRITE_SAFE_ROOT" with "protected system/credential file" — agent misdiagnoses and retry-loops #64003

Description

@marknisk

agent/file_safety.py: is_write_denied() returns a bare bool for four very different denial reasons:

  1. exact deny-list hit (~/.ssh/id_ed25519, .env, /etc/shadow, …),
  2. deny-prefix hit (~/.ssh/, ~/.aws/, /etc/systemd/, …),
  3. mcp-tokens / pairing dirs under a Hermes home,
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to havecomp/agentCore agent runtime: loop, agent_init, prompt builder, context-compression, responses endpointduplicateThis issue or pull request already existssweeper:implemented-on-mainSweeper: behavior already present on current maintool/fileFile tools (read, write, patch, search)type/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions