LSP — Semantic Diagnostics
تشخيص الكود عبر LSP
What this page is, and what it holds.
This page covers LSP — Semantic Diagnostics. You will use hermes lsp status and hermes lsp restart here; about 9 minutes to read. Change one thing at a time. Changing five at once makes a break impossible to trace.
Real language servers (pyright, gopls, rust-analyzer, …) wired into the post-write lint check used by writefile and patch.
Outcomes taken from this page, not a template.
- Understand what ملف الإعداد is and when you need it.
- Run
hermes lsp statusandhermes lsp restartand understand what happens next. - Read the table and take only the row that applies to you.
- Set
HERMES_HOMEin the right place.
Exactly as they appear in Hermes.
hermes lsp statushermes lsp restarthermes lsp listhermes lsp whichhermes lsp installhermes lsp install-allhermes lsp install typescript
HERMES_HOMEPYRIGHT_LOG_LEVEL
Jump to the part you need.
Nothing summarised away.
The documentation body below is reproduced from the official source so commands and identifiers stay exact. Each section carries a short note describing what it contains.
Hermes runs full language servers — pyright, gopls, rust-analyzer,
typescript-language-server, clangd, and ~20 more — as background
subprocesses and feeds their semantic diagnostics into the post-write
lint check used by write_file and patch. When the agent edits a
file, it sees exactly the errors that edit introduced — not just
syntax errors, but **type errors, undefined names, missing imports,
and project-wide semantic issues** the language server detects.
This is the same architecture top-tier coding agents use. Hermes ships it self-contained: no editor host required, no plugins to install, no separate daemon to manage.
When LSP runs
Explains the idea itself. Read it slowly; the later sections build on it.
LSP is gated on git workspace detection. When the agent's working directory (or the file being edited) is inside a git repository, LSP runs against that workspace. When neither is in a git repo, LSP stays dormant — useful for messaging gateways where the cwd is the user's home directory and there's no project to diagnose.
The check is layered: in-process syntax check first (microseconds), then LSP diagnostics second when syntax is clean. A flaky or missing language server can never break a write — every LSP failure path falls back silently to the syntax-only result.
Concretely, on every successful write_file or patch:
- Hermes captures a baseline of current diagnostics for the file.
- Performs the write.
- Re-queries the language server, filters out diagnostics that were already in the baseline, and surfaces only the new ones.
The agent sees output like:
{
"bytes_written": 42,
"dirs_created": false,
"lint": {"status": "ok", "output": ""},
"lsp_diagnostics": "LSP diagnostics introduced by this edit:\n<diagnostics file=\"/path/to/foo.py\">\nERROR [42:5] Cannot find name 'foo' [reportUndefinedVariable] (Pyright)\nERROR [50:1] Argument of type \"str\" is not assignable to \"int\" [reportArgumentType] (Pyright)\n</diagnostics>"
}The lint field carries the syntax-check result (microsecond
in-process parse via ast.parse, json.loads, etc.); the
lsp_diagnostics field carries the semantic diagnostics from the
real language server. Two channels, independent signals — the
agent sees a syntax-clean file with semantic problems as
`lint: ok plus a populated lsp_diagnostics`.
Supported languages
A lookup table. Do not read it all; find the row that applies to you. Commands here: hermes lsp status, hermes lsp install typescript.
| Language | Server | Auto-install |
|---|---|---|
| Python | pyright-langserver | npm |
| TypeScript / JavaScript / JSX / TSX | typescript-language-server | npm |
| Vue | @vue/language-server | npm |
| Svelte | svelte-language-server | npm |
| Astro | @astrojs/language-server | npm |
| Go | gopls | go install |
| Rust | rust-analyzer | manual (rustup) |
| C / C++ | clangd | manual (LLVM) |
| Bash / Zsh | bash-language-server | npm |
| YAML | yaml-language-server | npm |
| Lua | lua-language-server | manual (GitHub releases) |
| PHP | intelephense | npm |
| OCaml | ocaml-lsp | manual (opam) |
| Dockerfile | dockerfile-language-server-nodejs | npm |
| Terraform | terraform-ls | manual |
| Dart | dart language-server | manual (dart sdk) |
| Haskell | haskell-language-server | manual (ghcup) |
| Julia | julia + LanguageServer.jl | manual |
| Clojure | clojure-lsp | manual |
| Nix | nixd | manual |
| Zig | zls | manual |
| Gleam | gleam lsp | manual (gleam install) |
| Elixir | elixir-ls | manual |
| Prisma | prisma language-server | manual |
| Kotlin | kotlin-language-server | manual |
| Java | jdtls | manual |
| PowerShell | PowerShellEditorServices (pwsh host) | manual (release zip) |
For "manual" entries, install the server through whatever toolchain
manager makes sense for that language (rustup, ghcup, opam, brew,
…). Hermes auto-detects the binary on PATH or in
<HERMES_HOME>/lsp/bin/.
PowerShell
PowerShellEditorServices isn't a single binary — it's a PowerShell
module bundle launched by a pwsh (PowerShell 7+) or powershell
host. Setup:
- Install PowerShell ↗ so
pwsh(or Windowspowershell) is on PATH. - Download the latest release zip from PowerShellEditorServices releases ↗ and extract it.
- Point Hermes at the extracted bundle — the directory that contains
PowerShellEditorServices/Start-EditorServices.ps1. Either: - set
lsp.servers.powershell.command: ["/path/to/bundle"]inconfig.yaml, or - extract it to
<HERMES_HOME>/lsp/PowerShellEditorServices, or - export
PSES_BUNDLE_PATH=/path/to/bundle.
hermes lsp status reports installed once pwsh is found; if the
bundle is missing you'll see a one-time warning in the logs with the
download link.
A few servers are installed alongside a peer dependency that npm
won't auto-pull. The current case is typescript-language-server,
which requires the typescript SDK importable from the same
node_modules tree — Hermes installs both packages together when you
run hermes lsp install typescript or auto-install fires on first
use.
CLI
Ordered, practical steps. Run one and confirm it worked before moving on. Commands here: hermes lsp status, hermes lsp list.
hermes lsp status # service state + per-server install status
hermes lsp list # registry, optionally --installed-only
hermes lsp install <id> # eagerly install one server
hermes lsp install-all # try every server with a known recipe
hermes lsp restart # tear down running clients
hermes lsp which <id> # print resolved binary pathhermes lsp status is the best starting point — it shows which
languages will get semantic diagnostics today and which need a
binary installed.
Configuration
Settings you configure once. Change one at a time so you can see what each does. Set HERMES_HOME, PYRIGHT_LOG_LEVEL in your environment, not in the chat.
The defaults work for typical setups; nothing to set if the binaries are on PATH.
# config.yaml
lsp:
# Master toggle. Disabling skips the entire subsystem — no servers
# spawn, no background event loop runs.
enabled: true
# How long to wait for diagnostics after each write.
wait_mode: document # "document" or "full"
# Max seconds to wait for the server to re-check the file after an
# edit. Only *fresh* diagnostics (produced for the post-edit
# content) are ever reported; if the server doesn't finish within
# this budget, the edit reports "no LSP data" rather than stale
# errors from before the edit. Raise this for slow servers on big
# projects (tsserver, rust-analyzer mid-indexing).
wait_timeout: 5.0
# How to handle missing server binaries.
# auto — install via npm/pip/go install into <HERMES_HOME>/lsp/bin
# manual — only use binaries already on PATH
install_strategy: auto
# How long an unused language-server client stays alive (seconds).
# Idle servers are shut down automatically and respawned on the next
# relevant file operation. Set to 0 to disable idle reaping and keep
# servers alive for the life of the process. Values below 30s are
# clamped to 30 so a sweep can never reap a client mid-operation.
idle_timeout: 600
# Per-server overrides (all optional).
servers:
pyright:
disabled: false
command: ["/abs/path/to/pyright-langserver", "--stdio"]
env: { PYRIGHT_LOG_LEVEL: "info" }
initialization_options:
python:
analysis:
typeCheckingMode: "strict"
typescript:
disabled: true # skip TS even when its extensions matchPer-server keys
disabled: true— skip this server entirely even when its extensions match a file.command: [bin, ...args]— pin a custom binary path. Bypasses auto-install.env: {KEY: value}— extra env vars passed to the spawned process.initialization_options: {...}— merged into the LSPinitializationOptionspayload sent in theinitializehandshake. Server-specific; consult the language server's docs.
Installation locations
Ordered, practical steps. Run one and confirm it worked before moving on.
When install_strategy: auto, Hermes installs binaries into
<HERMES_HOME>/lsp/bin/. NPM packages land in
<HERMES_HOME>/lsp/node_modules/ with bin symlinks one level up.
Go binaries come from go install with GOBIN pointed at the
staging dir.
Nothing is ever installed to /usr/local/, ~/.local/, or any other
shared location — the staging dir is fully Hermes-owned and is
removed when you reset the profile.
Performance characteristics
Explains the idea itself. Read it slowly; the later sections build on it.
LSP servers are lazy-spawned on first use. Editing a Python file
in a project that's never seen .py traffic spawns pyright; the
spawn takes 1-3 seconds for most servers (rust-analyzer can take 10+
on a cold project). Subsequent edits in the same workspace re-use
the running server.
The LSP layer adds a few milliseconds to clean writes when no
diagnostics are emitted. When diagnostics are emitted, the wait
budget is wait_timeout seconds — typically the server responds in
tens of milliseconds for pyright/tsserver and a few seconds for
rust-analyzer mid-indexing.
Diagnostics are freshness-gated: a result only counts when the
server produced it for the content of the current edit (a
publishDiagnostics push at/after the change, or a pull request
answered after it). Slow servers that haven't re-checked yet result
in "no data" for that edit — never in yesterday's errors being
re-reported as current.
Servers are kept alive while they're being used and shut down after
lsp.idle_timeout seconds (default 600) with no file activity — a
long-running gateway that touches many worktrees no longer accumulates
one language-server process per workspace forever. A reaped server is
respawned automatically on the next relevant file operation. Set
idle_timeout: 0 to disable reaping and hold every server's index warm
for the life of the process.
Disabling
Settings you configure once. Change one at a time so you can see what each does.
Set lsp.enabled: false in config.yaml to disable the entire
subsystem. The post-write check falls back to the in-process syntax
check (ast.parse for Python, json.loads for JSON, etc.) which
ships unchanged from earlier versions.
To disable a single language without disabling the whole layer:
lsp:
servers:
rust-analyzer:
disabled: trueTroubleshooting
A troubleshooting section. Find the symptom that matches yours rather than reading it end to end. Commands here: hermes lsp status, hermes lsp restart.
hermes lsp status shows a server as "missing"
The binary isn't on PATH and isn't in <HERMES_HOME>/lsp/bin/. Run
hermes lsp install <server_id> to attempt an auto-install, or
install the binary manually through the language's normal toolchain.
Backend warnings section in hermes lsp status
Some servers ship as thin wrappers around an external CLI for actual
diagnostics — they spawn cleanly and accept requests but never emit
errors when the sidecar binary is missing. The most common case is
bash-language-server, which delegates diagnostics to shellcheck.
When hermes lsp status shows a Backend warnings section, install
the named tool through your OS package manager:
apt install shellcheck # Debian / Ubuntu
brew install shellcheck # macOS
scoop install shellcheck # WindowsThe same warning is logged once at server spawn time in
~/.hermes/logs/agent.log.
Server starts but never returns diagnostics
Check ~/.hermes/logs/agent.log for [agent.lsp.client] entries —
both stderr from the language server and protocol errors land
there. Some servers (rust-analyzer especially) need to finish a
project-wide index before they emit per-file diagnostics; the first
edit after server start may complete with no diagnostics, with
subsequent edits picking them up.
Server crashed
A crashed server is added to the broken-set and won't be retried for
the rest of the session. Run hermes lsp restart to clear the set;
the next edit re-spawns.
Editing a file outside any git repo
By design, LSP only runs inside a git repository. If the project isn't
yet initialized, run git init to enable LSP diagnostics. Otherwise the
in-process syntax-only fallback applies.
5 questions answered by this page alone.
Every option is a real identifier from the Hermes documentation. The wrong ones are real too, just from other pages.