claude-code - 💡(How to fix) Fix [BUG] LSP tool: @angular/language-server returns empty results — harness does not open companion .ts files, so template-to-component resolution fails [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#54814Fetched 2026-04-30 06:35:13
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
0
Author
Timeline (top)
labeled ×5commented ×1referenced ×1

Claude Code's LSP tool successfully spawns @angular/language-server (ngserver), but every template navigation operation against .html files returns empty ("No definition found" / "No hover information available"). Root cause: the harness sends textDocument/didOpen only for the file being queried. Angular Language Server requires the client to open the companion component .ts files (and ideally all workspace .ts) so it can build the project graph used to resolve template bindings.

This is the same family as #16804, #42013, #17312, #43100, #31364 (closed), but ngserver is a particularly clean reproduction because every Angular template query depends on workspace context — there is no "local symbol" fallback that masks the issue.

Root Cause

Claude Code's LSP tool successfully spawns @angular/language-server (ngserver), but every template navigation operation against .html files returns empty ("No definition found" / "No hover information available"). Root cause: the harness sends textDocument/didOpen only for the file being queried. Angular Language Server requires the client to open the companion component .ts files (and ideally all workspace .ts) so it can build the project graph used to resolve template bindings.

Code Example

LSP goToDefinition on .../security-group-chips.component.html line 13 char 14
  (cursor on \`group\` inside \`{{group.name}}\`)
=> \"No definition found. This may occur if the cursor is not on a symbol,
    or if the definition is in an external library not indexed by the LSP server.\"

LSP hover on the same position
=> \"No hover information available. This may occur if the cursor is not on a symbol,
    or if the LSP server has not fully indexed the file.\"

LSP documentSymbol on the same file
=> \"Unhandled method textDocument/documentSymbol\"
   (expected — Angular LSP doesn't implement documentSymbol for HTML; this just confirms
    the server is alive and responding to LSP requests, ruling out a hang or transport issue.)

---

{
     \"angular\": {
       \"command\": \"ngserver\",
       \"args\": [
         \"--stdio\",
         \"--tsProbeLocations\", \"/abs/path/to/angular/workspace/node_modules\",
         \"--ngProbeLocations\", \"/abs/path/to/angular/workspace/node_modules\"
       ],
       \"transport\": \"stdio\",
       \"extensionToLanguage\": { \".html\": \"html\" }
     }
   }
RAW_BUFFERClick to expand / collapse

Summary

Claude Code's LSP tool successfully spawns @angular/language-server (ngserver), but every template navigation operation against .html files returns empty ("No definition found" / "No hover information available"). Root cause: the harness sends textDocument/didOpen only for the file being queried. Angular Language Server requires the client to open the companion component .ts files (and ideally all workspace .ts) so it can build the project graph used to resolve template bindings.

This is the same family as #16804, #42013, #17312, #43100, #31364 (closed), but ngserver is a particularly clean reproduction because every Angular template query depends on workspace context — there is no "local symbol" fallback that masks the issue.

Environment

  • Claude Code: 2.1.123 (claude --version)
  • Platform: macOS (Darwin 25.3.0, arm64)
  • Node: v22.21.1
  • LSP server: ngserver from @angular/[email protected] (installed globally)
  • Workspace: Angular CLI multi-project workspace, Angular 21.0.9, with @angular/[email protected] in node_modules/
  • Plugin under test: locally maintained lsp-servers@w3geekery-lsps (https://github.com/w3geekery/claude-code-lsps), .lsp.json at plugin root with valid lspServers.angular entry including --tsProbeLocations and --ngProbeLocations pointing at the repo's node_modules.

What works

  • TypeScript LSP (typescript-lsp@claude-plugins-official) — works correctly against .ts files.
  • some-sass-language-server (registered via the same plugin) — works correctly against .scss files (e.g. documentSymbol returns mixin/function/variable list).
  • ngserver itself — confirmed to start cleanly in standalone mode when given probe locations. Crashes without them ("Failed to resolve typescript/lib/tsserverlibrary"), so the registration is being read correctly by the harness.

What's broken

After plugin install + Claude Code restart, against any .html file in the Angular workspace:

LSP goToDefinition on .../security-group-chips.component.html line 13 char 14
  (cursor on \`group\` inside \`{{group.name}}\`)
=> \"No definition found. This may occur if the cursor is not on a symbol,
    or if the definition is in an external library not indexed by the LSP server.\"

LSP hover on the same position
=> \"No hover information available. This may occur if the cursor is not on a symbol,
    or if the LSP server has not fully indexed the file.\"

LSP documentSymbol on the same file
=> \"Unhandled method textDocument/documentSymbol\"
   (expected — Angular LSP doesn't implement documentSymbol for HTML; this just confirms
    the server is alive and responding to LSP requests, ruling out a hang or transport issue.)

The component class (SecurityGroupChipsComponent) is defined in the sibling .ts file with @Component({ templateUrl: './security-group-chips.component.html' }), declares public activeGroups = [];, and references it from the template via @for (group of activeGroups; ...). A correctly-driven Angular LSP can resolve this.

Why this is a harness issue, not a server / config issue

  1. VSCode's official Angular extension drives the same ngserver binary against the same workspace and resolves every binding correctly. That extension's behavior at startup includes a workspace-wide textDocument/didOpen sweep over all .ts files matching the project's tsconfig. It also injects rootUri / workspaceFolders from the VSCode workspace.

  2. @angular/language-server documents this requirement: its design (https://github.com/angular/vscode-ng-language-service/wiki/Design) explicitly relies on the client opening project files so the embedded tsserver + Angular plugin can build the program. Per-file didOpen for .html only is insufficient; the server has no way to know which .ts files declare the components referenced by that template.

  3. Empirically: the Claude Code harness sends didOpen only for the file the user runs LSP against. Confirmed by the symptom pattern (server alive, responds, returns empty for cross-file queries) and by the existing reports in #16804 / #32067 / #32499.

Reproduction

  1. npm install -g @angular/language-server typescript

  2. Install a Claude Code plugin that registers ngserver for .html. Minimal .lsp.json:

    {
      \"angular\": {
        \"command\": \"ngserver\",
        \"args\": [
          \"--stdio\",
          \"--tsProbeLocations\", \"/abs/path/to/angular/workspace/node_modules\",
          \"--ngProbeLocations\", \"/abs/path/to/angular/workspace/node_modules\"
        ],
        \"transport\": \"stdio\",
        \"extensionToLanguage\": { \".html\": \"html\" }
      }
    }
  3. From within an Angular CLI workspace, run LSP goToDefinition on any property reference inside an {{interpolation}} or structural directive in a component's template. Expected: jump to the property declaration in the sibling .ts. Actual: "No definition found."

Suggested fix (per #31364 discussion)

The harness needs, on LSP initialize or shortly after initialized:

  1. Detect the workspace root for the file being queried (walk up from filePath to find tsconfig.json / package.json / angular.json / a configurable marker).
  2. Send rootUri / workspaceFolders in the initialize request.
  3. After initialized, for any LSP server that registers extensions implying a multi-file project (TS, Angular, Java, Rust, Scala, etc.), enumerate matching files in the workspace and send textDocument/didOpen for each. This is the behavior every "real" LSP client (VSCode, Neovim+nvim-lspconfig, Emacs+lsp-mode, Helix) implements.

A more conservative variant: when LSP <op> is called against a .html file handled by an LSP that also handles .ts/.tsx, also didOpen the sibling .ts files in the same directory before forwarding the request. That alone would fix the most common Angular case (component + template colocated), without a full workspace sweep.

Why this matters beyond Angular

Any LSP that builds a cross-file project graph hits this — TypeScript with project references, Rust analyzer with cargo workspaces, gopls with multi-package modules, jdtls with Maven/Gradle. The Angular case is just an unusually clean repro because it has a hard cross-file dependency (template → component) with no fallback.

Related

  • #16804 — typescript-lsp didOpen never sent (open)
  • #42013 — LSP hangs after init (open, stale)
  • #17312 — LSP empty results despite server responding (open)
  • #43100 — LSP servers lost during reinitialize (open)
  • #31364 — No cross-file indexing (closed) — same root cause
  • #31365 — rootUri null on Windows (closed)
  • #32067 / #32499 — empty hover/goToDefinition (closed, stale)

extent analysis

TL;DR

The harness needs to send textDocument/didOpen for all relevant files in the workspace, not just the file being queried, to allow the Angular Language Server to build the project graph.

Guidance

  • Detect the workspace root for the file being queried by walking up from filePath to find tsconfig.json / package.json / angular.json / a configurable marker.
  • Send rootUri / workspaceFolders in the initialize request to provide context for the LSP server.
  • Enumerate matching files in the workspace and send textDocument/didOpen for each after initialized, or as a minimum, didOpen the sibling .ts files in the same directory when LSP <op> is called against a .html file.
  • Consider implementing a full workspace sweep for LSP servers that require it, such as TypeScript with project references or Rust analyzer with cargo workspaces.

Example

A minimal example of sending textDocument/didOpen for a sibling .ts file:

{
  "method": "textDocument/didOpen",
  "params": {
    "textDocument": {
      "uri": "file:///path/to/sibling.ts",
      "languageId": "typescript",
      "version": 1,
      "text": "..."
    }
  }
}

Notes

This fix is not specific to Angular and may apply to other LSP servers that build cross-file project graphs, such as TypeScript, Rust, or Java.

Recommendation

Apply the suggested fix by modifying the harness to send textDocument/didOpen for all relevant files in the workspace, as this will allow the Angular Language Server to build the project graph and provide accurate results for template navigation operations.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] LSP tool: @angular/language-server returns empty results — harness does not open companion .ts files, so template-to-component resolution fails [1 comments, 2 participants]