{"record":{"id":"a8abdc3e93001be4","repo":"NousResearch/hermes-agent","slug":"cannot-edit-non-file-path-path","errorCode":null,"errorMessage":"Cannot edit non-file path: {path}","messagePattern":"Cannot edit non-file path: (.+?)","errorType":"exception","errorClass":"OSError","httpStatus":null,"severity":"error","filePath":"acp_adapter/edit_approval.py","lineNumber":78,"sourceCode":"    _EDIT_APPROVAL_REQUESTER.reset(token)\n\n\ndef clear_edit_approval_requester() -> None:\n    \"\"\"Clear the current requester; primarily used by tests.\"\"\"\n\n    _EDIT_APPROVAL_REQUESTER.set(None)\n\n\ndef get_edit_approval_requester() -> EditApprovalRequester | None:\n    return _EDIT_APPROVAL_REQUESTER.get()\n\n\ndef _read_text_if_exists(path: str) -> str | None:\n    p = Path(path).expanduser()\n    if not p.exists():\n        return None\n    if not p.is_file():\n        raise OSError(f\"Cannot edit non-file path: {path}\")\n    return p.read_text(encoding=\"utf-8\", errors=\"replace\")\n\n\ndef _proposal_for_write_file(arguments: dict[str, Any]) -> EditProposal:\n    path = str(arguments.get(\"path\") or \"\")\n    if not path:\n        raise ValueError(\"path required\")\n    content = arguments.get(\"content\")\n    if content is None:\n        raise ValueError(\"content required\")\n    return EditProposal(\n        tool_name=\"write_file\",\n        path=path,\n        old_text=_read_text_if_exists(path),\n        new_text=str(content),\n        arguments=dict(arguments),\n    )\n","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/acp_adapter/edit_approval.py#L60-L96","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\np = Path(path).expanduser()\nif not p.is_file():\n    raise OSError(f\"Cannot edit non-file path: {path}\")\n\n# after — validate in the proposal builder before touching the FS\np = Path(path).expanduser()\nif p.is_dir():\n    raise OSError(f\"Cannot edit non-file path: {path}\")","handlingStrategy":"type-guard","validationCode":"from pathlib import Path\np = Path(path).expanduser()\nif p.exists() and not p.is_file():\n    return error_response(f\"Refusing to edit non-file: {path}\")  # before building a proposal","typeGuard":"def is_editable_file(path: str) -> bool:\n    p = Path(path).expanduser()\n    return p.is_file()","tryCatchPattern":"try:\n    proposal = _build_proposal(tool_name, arguments)\nexcept OSError as err:\n    # return structured ACP error, not a stack trace\n    return {\"error\": {\"code\": -32602, \"message\": str(err)}}","preventionTips":["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"],"tags":["acp","editor-integration","filesystem","validation","python"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}