Skip to content

PrepareRename throws NullReferenceException when an LSP client omits the rename capability (v4.6.0) #2297

@manderse21

Description

@manderse21

Summary

In PSES v4.6.0, an LSP initialize whose capabilities.textDocument omits
rename leaves the prepare-rename handler dereferencing a null client
RenameCapability. Observed effect: the server never returns an initialize
response (the handshake hangs) and the PSES log records a NullReferenceException
on the rename-handler path.

Declaring even a minimal rename capability avoids it and the handshake
completes normally. This inverts the usual expectation that omitting an optional
client capability is always safe, so it is easy to hit from a minimal or hand-rolled
LSP client.

Environment

  • PSES: v4.6.0 (GitHub release asset PowerShellEditorServices.zip)
  • Host: PowerShell 7.6.2 (pwsh) and Windows PowerShell 5.1 -- reproduced on both
  • OS: Windows 11
  • Transport: Start-EditorServices.ps1 -Stdio

Steps to reproduce

  1. Launch PSES over stdio (paths trimmed for readability):

    pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File <PSES>/Start-EditorServices.ps1 \
      -HostName repro -HostProfileId repro -HostVersion 1.0.0 -Stdio \
      -BundledModulesPath <bundle> -LogPath <log> -LogLevel Diagnostic \
      -SessionDetailsPath <session.json>
    
  2. Send an LSP initialize whose textDocument capabilities cover the usual
    entries but omit rename:

    {
      "capabilities": {
        "textDocument": {
          "synchronization": { "didSave": true },
          "publishDiagnostics": { "relatedInformation": true },
          "hover": {},
          "definition": {}
        }
      }
    }
  3. Observe: no initialize response is returned (the handshake hangs), and the
    PSES log shows a NullReferenceException in the rename-handler path.

The one-line delta that proves the cause

Re-send initialize with a minimal rename capability declared and nothing else
changed:

{
  "capabilities": {
    "textDocument": {
      "synchronization": { "didSave": true },
      "publishDiagnostics": { "relatedInformation": true },
      "hover": {},
      "definition": {},
      "rename": { "prepareSupport": false }
    }
  }
}

The handshake now completes and diagnostics flow. The only difference is the
presence of the rename capability object.

Expected

Omitting the optional rename capability must not break initialize. The
prepare-rename handler should null-guard the client RenameCapability (treat absent
as "client does not support rename") rather than dereferencing it.

Context

Found while building a Claude Code plugin that drives PSES over stdio
(https://github.com/manderse21/claude-powershell-lsp). The plugin now declares a
minimal rename capability specifically to dodge this, but the root cause should be
fixed for any client that legitimately omits it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions