NousResearch/hermes-agent · error · LSPProtocolError

cannot read {abs_path}: {e}

Error message

cannot read {abs_path}: {e}

What it means

LSPProtocolError raised by open_file when reading the local file to send in didOpen/didChange fails with an OSError. The client must send the document text to the server, so an unreadable or missing path aborts the sync. Note errors='replace' is already set, so encoding problems do NOT trigger this — only real I/O failures.

Source

Thrown at agent/lsp/client.py:736

    # ------------------------------------------------------------------
    # public file-sync API
    # ------------------------------------------------------------------

    async def open_file(self, path: str, *, language_id: str = "plaintext") -> int:
        """Send didOpen (first time) or didChange (subsequent) for ``path``.

        Returns the new document version number that the agent's
        ``wait_for_diagnostics`` should match against.
        """
        if not self.is_running:
            raise LSPProtocolError("client not running")

        abs_path = os.path.abspath(path)
        try:
            text = Path(abs_path).read_text(encoding="utf-8", errors="replace")
        except OSError as e:
            raise LSPProtocolError(f"cannot read {abs_path}: {e}") from e

        uri = file_uri(abs_path)
        doc = self._docs.get(abs_path)

        if doc is not None and doc.version >= 0:
            # Re-open: bump version, fire didChangeWatchedFiles + didChange.
            await self._send_notification(
                "workspace/didChangeWatchedFiles",
                {"changes": [{"uri": uri, "type": 2}]},  # 2 = CHANGED
            )
            new_version = doc.version + 1
            old_text = doc.text
            content_changes: List[Dict[str, Any]]
            if self._sync_kind == 2:
                content_changes = [
                    {
                        "range": {
                            "start": {"line": 0, "character": 0},

View on GitHub (pinned to c896c09c42)

Solutions

  1. Validate the file exists and is readable before syncing (Path.is_file() plus an os.access check).
  2. If the file was renamed/deleted intentionally, close it on the server side (didClose) instead of opening it.
  3. Fix permissions or run the agent with read access to the workspace root.

Example fix

# before
await client.open_file(deleted_path)  # LSPProtocolError: cannot read

# after
from pathlib import Path
p = Path(path)
if not p.is_file():
    await client.close_file(path)  # sync deletion with the server
else:
    await client.open_file(path)
Defensive patterns

Strategy: validation

Validate before calling

import os
from pathlib import Path

def file_readable(path: str) -> bool:
    p = Path(path)
    return p.is_file() and os.access(p, os.R_OK)

Try / catch

try:
    version = await client.open_file(path)
except LSPProtocolError as e:
    if "cannot read" in str(e):
        skip_or_close_document(path)  # deleted/unreadable: sync deletion instead
    else:
        raise

Prevention

When it happens

Trigger: open_file on a path that was deleted between listing and sync; a directory passed instead of a file; permission denied (mode 000, foreign ownership); path too long or otherwise invalid for the OS.

Common situations: Editor/agent racing file deletion or rename (git checkout switching branches); paths from external systems that no longer exist; sandboxed environments without read access to the workspace.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/4308f8592994d0df. Report an issue: GitHub.