[BUG] LSP workspaceSymbol requires a filePath it never uses, and rejects the workspace root

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Aug 4, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

workspaceSymbol requires a filePath that it does not use, and rejects the one value a caller would
naturally supply for a workspace-wide search.

The LSP tool applies a single signature — filePath, line, character — to all nine operations. For
workspaceSymbol, whose own description is *"Search for symbols matching a query across the entire
workspace"*, none of the three affects the result. line and character are ignored outright. filePath
only selects which language server handles the request — but it is validated strictly as an existing regular
file, so the workspace root, ., or any directory is refused.

The result is a required parameter with no correct value for the operation's semantics. Callers reach for the
workspace root, get an error, and have to nominate an arbitrary unrelated source file to proceed.

Measured across independent sessions (each a fresh claude -p, prompted only with a navigation question and
never given a path):

  • 9 of 9 sessions on a small Python project used filePath: "." for their first workspaceSymbol call

and failed. No session that chose its own path ever reached a valid one first.

  • 38 rejected calls across 34 sessions on a 12k-file PHP/JavaScript repository:

| filePath supplied | error | count |
| ---------------------------------- | ---------------------------- | ----- |
| workspace root (absolute) | Path is not a file: … | 34 |
| . | Path is not a file: . | 2 |
| a directory (app/Models) | Path is not a file: … | 1 |
| a path that does not exist | File does not exist: … | 1 |

Every session recovered by retrying against a real file, so the cost is a wasted round-trip rather than a
wrong answer — but it lands on the first navigation step, so most sessions pay it. Recovery is not always one
call: several sessions responded to the rejection by shelling out to find and git ls-files to hunt for a
file they were allowed to anchor at, spending three or four more calls before retrying.

What Should Happen?

workspaceSymbol should be callable without inventing a file path. In rough order of preference:

  1. Stop requiring parameters that cannot affect the result. Make filePath, line and character

optional for workspaceSymbol (and line/character for documentSymbol). When filePath is absent,
resolve the server internally — from the workspace root, or from the single configured server when there is
only one.

  1. Accept a directory for workspaceSymbol and resolve an anchor file inside it.
  2. Failing either, say so — in the parameter description and in the error itself. This alone would likely

stop the observed behaviour:

``
Path is not a file: "." — for workspaceSymbol, filePath may be any existing source file in the
project; it selects the language server and does not narrow the search.
``

Option 3 is the minimal change. Option 1 removes the trap rather than documenting it.

Error Messages/Logs

The reported failure:


LSP(operation: "workspaceSymbol", filePath: ".", line: 1, character: 1, query: "CalendarStore")
→ <tool_use_error>Path is not a file: .</tool_use_error>


Two distinct validation messages, neither of which states what a valid value would be:


filePath: "."                → Path is not a file: .
filePath: "app/Models"       → Path is not a file: app/Models
filePath: "src/missing.py"   → File does not exist: src/missing.py


And the same call with an arbitrary unrelated file succeeds, returning a symbol from a *different* file, with
an out-of-range position accepted silently — which is what demonstrates the parameters are unused:


LSP(operation: "workspaceSymbol", filePath: "hooks/lib/guard_read.py",
    line: 99999, character: 99999, query: "looks_like_symbol")
→ Found 1 symbol in workspace:
    hooks/lib/lspfirst.py:
      looks_like_symbol (Function) - Line 357

Steps to Reproduce

  1. Enable any LSP plugin whose server binary resolves on PATH — e.g. pyright-lsp@claude-plugins-official

with pyright-langserver.

  1. Open a project containing at least one named symbol (a class or function).
  2. Invoke the LSP tool with the workspace root as the anchor:

``
LSP(operation: "workspaceSymbol", filePath: ".", line: 1, character: 1, query: "<SymbolName>")
``

  1. Observe <tool_use_error>Path is not a file: .</tool_use_error>. The same applies to an absolute path to

the workspace root, or to any directory within it.

  1. Repeat with filePath set to any existing source file — including one that does not contain the symbol,

and with line/character set to out-of-range values. The query succeeds and returns the symbol from
whichever file actually defines it, confirming that none of the three parameters affects the result.

To see it arise unprompted rather than by construction: start a fresh session in a project with an LSP plugin
enabled and ask a navigation question that names no path, e.g. *"Where is class X defined? Use the language
server."*

Claude Model

Not sure / Multiple models

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.220 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Terminal.app (macOS)

Additional Information

Prior report, now unreachable. #21655 documented
this exact behaviour and identified the same cause: a uniform filePath/line/character interface applied
to an operation that needs only a query. It was closed not_planned on 2026-02-28 and is now locked, so
it cannot be reopened or commented on. That is the only reason this is filed fresh rather than as a follow-up.

Why the current documentation does not prevent it. The tool description says, of all nine operations:

All operations require: - filePath: The file to operate on

and the parameter schema says only "The absolute or relative path to the file". For workspaceSymbol, "the
file to operate on" does not exist as a concept. Nothing states that the path must exist, that any file in the
project will do, or that line/character are ignored. A caller reasoning about a workspace-wide search has
no way to arrive at "any real file at all, but it must be real".

Models and how the measurements were taken. Each data point is an independent non-interactive session
(claude -p) with a fresh workspace, so no session could learn from another's failure. The 9-of-9 Python
result was 7 sessions on claude-haiku-4-5 and 2 on claude-sonnet-5 — the behaviour is not an artefact of a
smaller model guessing. The 38-rejection PHP/JavaScript result was claude-haiku-4-5 with php-lsp and
typescript-lsp enabled.

documentSymbol is affected by the same interface problem, less visibly. It also ignores
line/character — passing line: 99999, character: 99999 returns the document's full symbol list — but
because its filePath genuinely is "the file to operate on", it has no rejected intuitive value and so costs
nothing. Only workspaceSymbol produces a failed call.

Context. This surfaced while building a plugin that routes code navigation through the LSP tool, so
sessions reach for workspaceSymbol as the first step far more often than they otherwise would. That makes
the wasted round-trip systematic rather than occasional, which is why it was worth measuring.

View original on GitHub ↗