NousResearch/hermes-agent · error · LSPProtocolError
client not running
Error message
client not running
What it means
LSPProtocolError raised by open_file when client.is_running is false — the caller asked to sync a file with a server that is not alive. It is a lifecycle misuse error: open_file must be called between start() and stop()/server exit.
Source
Thrown at agent/lsp/client.py:730
# keep the Event sticky-set so any wait already in progress
# resolves; waiters re-check their predicate after waking and
# decide whether to keep waiting. ``_push_counter`` is what
# they actually compare against to detect a fresh event.
self._push_counter += 1
self._push_event.set()
# ------------------------------------------------------------------
# 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.textView on GitHub (pinned to c896c09c42)
Solutions
- Await start() before any file sync, and guard every open_file with an is_running check.
- If the server crashed, restart the client (fresh process, re-open all relevant documents) rather than reusing the dead one.
- Propagate startup errors instead of ignoring them, so a not-running client is never silently used.
Example fix
# before
await client.open_file("src/main.py") # may raise: client not running
# after
if not client.is_running:
await client.start()
await client.open_file("src/main.py") Defensive patterns
Strategy: type-guard
Validate before calling
if not client.is_running:
await client.start()
version = await client.open_file(path) Type guard
async def ensure_running(client) -> bool:
"""True if the client is alive; starts it if not yet started."""
if client.is_running:
return True
try:
await client.start()
return client.is_running
except LSPProtocolError:
return False Prevention
- Always await start() and check is_running before file-sync calls.
- Never swallow startup exceptions — a half-started client guarantees this error later.
- Guard all file-sync entry points (open_file/close_file) with an is_running check.
When it happens
Trigger: Calling open_file before start() completed, after stop(), or after the server process exited on its own; also when start() failed earlier (e.g. binary not found) and the failure was swallowed.
Common situations: Fire-and-forget startup where callers do not await start(); a crashed server leaving a half-alive client object; test fixtures reusing a stopped client.
Related errors
- cannot send {method!r}: stdin closed
- Could not repair the desktop installation ID.
- Hermes backend start was superseded by a newer connection at
- LSP server binary not found: {cmd[0]} ({e})
- send failed for {method!r}: {e}
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/d8530cddabb2240d.
Report an issue: GitHub.