NousResearch/hermes-agent · error · OSError
Cannot edit non-file path: {path}
Error message
Cannot edit non-file path: {path} What it means
Raised by _read_text_if_exists in the ACP adapter's edit-approval layer (VS Code/Zed/JetBrains integration). When building an EditProposal for an incoming write_file/patch call, it reads the current file content to show the user a before/after diff. If the path exists but is not a regular file (directory, fifo/socket, broken symlink to a special file), it raises OSError because there is no text to diff against. This is a guard: edit approvals only make sense for real files.
Source
Thrown at acp_adapter/edit_approval.py:78
_EDIT_APPROVAL_REQUESTER.reset(token)
def clear_edit_approval_requester() -> None:
"""Clear the current requester; primarily used by tests."""
_EDIT_APPROVAL_REQUESTER.set(None)
def get_edit_approval_requester() -> EditApprovalRequester | None:
return _EDIT_APPROVAL_REQUESTER.get()
def _read_text_if_exists(path: str) -> str | None:
p = Path(path).expanduser()
if not p.exists():
return None
if not p.is_file():
raise OSError(f"Cannot edit non-file path: {path}")
return p.read_text(encoding="utf-8", errors="replace")
def _proposal_for_write_file(arguments: dict[str, Any]) -> EditProposal:
path = str(arguments.get("path") or "")
if not path:
raise ValueError("path required")
content = arguments.get("content")
if content is None:
raise ValueError("content required")
return EditProposal(
tool_name="write_file",
path=path,
old_text=_read_text_if_exists(path),
new_text=str(content),
arguments=dict(arguments),
)
View on GitHub (pinned to c896c09c42)
Solutions
- Correct the caller to target a regular file path (strip trailing slashes, verify with ls -l).
- If patching a directory rename/deletion, handle those patch hunks separately instead of routing them through _read_text_if_exists.
- Pre-resolve symlinks and check Path.is_file() before issuing the edit from the client.
- In the ACP handler, catch OSError and return a structured 'invalid path' response to the editor instead of a stack trace.
Example fix
# before
p = Path(path).expanduser()
if not p.is_file():
raise OSError(f"Cannot edit non-file path: {path}")
# after — validate in the proposal builder before touching the FS
p = Path(path).expanduser()
if p.is_dir():
raise OSError(f"Cannot edit non-file path: {path}") Defensive patterns
Strategy: type-guard
Validate before calling
from pathlib import Path
p = Path(path).expanduser()
if p.exists() and not p.is_file():
return error_response(f"Refusing to edit non-file: {path}") # before building a proposal Type guard
def is_editable_file(path: str) -> bool:
p = Path(path).expanduser()
return p.is_file() Try / catch
try:
proposal = _build_proposal(tool_name, arguments)
except OSError as err:
# return structured ACP error, not a stack trace
return {"error": {"code": -32602, "message": str(err)}} Prevention
- Strip trailing slashes and resolve symlinks before sending edit paths
- Route directory entries and special files away from the edit-approval builder
- Validate Path.is_file() client-side in editor extensions
When it happens
Trigger: An editor/agent sends a write_file or patch whose path points at a directory, /dev/null-like device, or named pipe; a symlink chain resolving to a directory; patch headers with a trailing-slash path ('src/dir/') parsed as a file edit.
Common situations: Diff/patch payloads that include directory rename or file-deletion entries being routed through the edit-approval builder; agent mistakenly targeting a folder path with write_file; tools that treat /dev/stdin or fifos as editable.
Related errors
- path required
- content required
- old_string and new_string required
- Failed to read file: {path}
- patch content required
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/a8abdc3e93001be4.
Report an issue: GitHub.