[BUG] LSP tool is pruned from all subagent tool sets in interactive sessions (present in the parent, and in subagents under -p)

Status Open
Reported on v2.1.221
Maintainer reply None cached
Activity 3 comments · opened Aug 5, 2026

Summary

In an interactive Claude Code session,
the LSP tool is absent from every dispatched subagent's active tool set
and from its deferred-tool registry,
so no prompt wording and no agent configuration can reach it.
The parent session in the very same process has LSP available
and returns real language-server data.
The same dispatch under claude -p (headless) does give the subagent LSP,
in all six --permission-mode values.

The subagent receives this error, which is factually wrong:

Error: No such tool available: LSP. LSP is disabled for this session, in subagents as well as here.

Both clauses are false.
LSP is not disabled for the session,
and it is not unavailable outside subagents —
the parent loads the schema and calls it successfully minutes later
in the same session, every time.

This reproduces in a clean room
a fresh config directory, a freshly issued token,
a throwaway two-line Python project,
and pyright-lsp as the only installed plugin,
with no CLAUDE.md, hooks, skills, or custom agents anywhere.
See "Clean-room reproduction" below.
Local configuration is not involved.

I'm happy to run further diagnostics.

Environment

  • Claude Code 2.1.221 and 2.1.222 (both reproduce)
  • Linux (Ubuntu, kernel 7.0.0-1005-gcp), bash
  • Model: reproduced on both Opus 5 and Sonnet 5 — model-independent
  • Plugin: pyright-lsp@claude-plugins-official v1.0.0, enabled in ~/.claude/settings.json
  • pyright-langserver on PATH; pyright 1.1.410 (npm global)
  • Python project, probing a .py file

The plugin install is correct — please don't close this as a config problem.
pyright-lsp declares its server in the marketplace ROOT manifest,
~/.claude/plugins/marketplaces/claude-plugins-official/.claude-plugin/marketplace.json:

"name": "pyright-lsp", "version": "1.0.0",
"source": "./plugins/pyright-lsp",
"lspServers": {
  "pyright": {
    "command": "pyright-langserver",
    "args": ["--stdio"],
    "extensionToLanguage": { ".py": "python", ".pyi": "python" }
  }
}

There is no per-plugin plugin.json for LSP plugins,
and the marketplace source checkout for plugins/pyright-lsp/
legitimately contains only LICENSE and README.md.
Twelve plugins in that manifest declare lspServers the same way.
The language server itself demonstrably works —
the parent gets correct documentSymbol output from it.

Reproduction

  1. Install pyright-lsp and npm install -g pyright.
  2. Start an interactive session in a Python project: cd $PROJECT_PATH && claude
  3. As the very first action, dispatch a plain general-purpose subagent and tell it to call

LSP with operation=documentSymbol, a real .py filePath, line=1, character=1,
without calling ToolSearch first.

  1. The subagent reports the error above.

Ask it to run ToolSearch with select:LSP — it gets No matching deferred tools found.
Ask it to list its deferred tools — LSP is not among them.

  1. Now, in the same session, the parent runs ToolSearch with select:LSP:

the full schema loads (9 operations).
The parent then calls LSP documentSymbol on the same file: real symbol data.

  1. Contrast — the same dispatch headless works:
claude -p "Dispatch a subagent with subagent_type='general-purpose' and have it call LSP \
with operation=documentSymbol, filePath=$PROJECT_PATH/path/to/module.py, line=1, character=1, \
without calling ToolSearch first. Report the outcome." \
  --allowedTools Agent LSP ToolSearch Read

The headless subagent gets LSP and returns real symbol data.

Clean-room reproduction — rules out all local configuration

To eliminate every piece of local setup,
the interactive reproduction was repeated against a virgin configuration:

mkdir -p /tmp/clean-config /tmp/clean-proj
printf 'class Demo:\n    def method(self, x: int) -> str:\n        return str(x)\n' > /tmp/clean-proj/demo.py
cd /tmp/clean-proj && CLAUDE_CONFIG_DIR=/tmp/clean-config claude

In that session: accepted the trust dialog,
authenticated with a newly issued token,
installed pyright-lsp@claude-plugins-official as the only plugin,
then restarted so the plugin registered at startup.

This configuration has no global or project CLAUDE.md,
no hooks, no skills, no custom agent definitions,
no MCP servers beyond the built-in claude_ai_* auth stubs,
and no other plugins.

The split reproduces exactly.

Parent, direct LSP documentSymbol call on demo.py, no ToolSearch first:

Document symbols:
Demo (Class) - Line 1
  method (Method) - Line 2
    x (Variable) - Line 2

Subagent (general-purpose), identical call:

Error: No such tool available: LSP. LSP is disabled for this session, in subagents as well as here.

LSP was in neither the subagent's active set nor its deferred list.
That deferred list was, verbatim:
EnterWorktree, ExitWorktree, Monitor, NotebookEdit, SendMessage, TaskStop,
WebFetch, WebSearch
plus the mcp__claude_ai_* auth stubs —
the same eight built-in entries seen in every other session,
confirming the withheld set is constant and independent of configuration.

One further detail from this run:
the parent's direct call succeeded without any ToolSearch,
even though LSP was listed as deferred with no schema loaded.
So for the parent the deferred contract is advisory,
while for the subagent the tool genuinely does not exist by either path.

Expected vs actual

Expected: a subagent dispatched from a session where an LSP server is configured
and working should be able to use the LSP tool,
either directly or after ToolSearch.

Actual: interactive subagents cannot reach it by any path,
and are told it is disabled session-wide, which is untrue.

Evidence

Interactive: 8 of 8 dispatches ABSENT, across four permission modes

Four fresh interactive sessions on 2.1.222, each dispatching a general-purpose subagent
at its startup mode and again after a Shift+Tab mode cycle
(--model claude-sonnet-5 throughout, to avoid a concurrent provider incident):

| Startup mode | Dispatch 1 (startup mode) | Cycled to | Dispatch 2 (new mode) | Parent, same session |
|---------------|---------------------------|---------------|-----------------------|------------------------------|
| auto | ABSENT | manual | ABSENT | LSP loads, returns real data |
| manual | ABSENT | acceptEdits | ABSENT | LSP loads, returns real data |
| acceptEdits | ABSENT | auto | ABSENT | LSP loads, returns real data |
| dontAsk | ABSENT | manual | ABSENT | LSP loads, returns real data |

All eight subagents reported the identical 8-tool active set,
an identical ~74-entry deferred list,
and a character-for-character identical error string.

Headless -p: LSP present in all six modes

| --permission-mode | Subagent LSP |
|-----------------------------------------------------------------|-------------------------------------------------------------------|
| manual, dontAsk, bypassPermissions, acceptEdits, plan | available, callable directly |
| auto | available as a deferred tool (ToolSearch loads it, then it works) |

Note the direct contradiction:
interactive auto → ABSENT, headless auto → available.
Same version, same machine, same plugin, same permission mode.

Exhibit A — the deferred registry is scoped per agent

Parent's deferred-tool list (verbatim, interactive session):

CronCreate, CronDelete, CronList, DesignSync, EndConversation, EnterPlanMode,
EnterWorktree, ExitPlanMode, ExitWorktree, LSP, ListMcpResourcesTool, Monitor,
NotebookEdit, PushNotification, ReadMcpResourceDirTool, ReadMcpResourceTool,
RemoteTrigger, SendMessage, TaskCreate, TaskGet, TaskList, TaskOutput, TaskStop,
WebFetch, WebSearch

…plus all MCP tools.

Subagent's deferred-tool list (verbatim, same session):

EnterWorktree, ExitWorktree, Monitor, NotebookEdit, SendMessage, TaskStop,
WebFetch, WebSearch

…plus all MCP tools.

The subagent's active set is exactly:

Agent, Artifact, Bash, Edit, Read, Skill, ToolSearch, Write

The 18 entries withheld from subagents are a coherent
orchestration / session-control family —
Cron*, Task*, Enter/ExitPlanMode, DesignSync, RemoteTrigger,
PushNotification, EndConversation, ReadMcpResource*, ListMcpResourcesTool
and LSP has apparently been swept in with them.
A code-navigation tool grouped with cron jobs and plan mode
looks like a classification mistake rather than an intentional restriction.
This also explains why no prompt wording helps:
a subagent cannot ToolSearch its way to a tool the harness never granted it.

Exhibit B — tools: frontmatter is honored for everything except LSP

A custom agent at .claude/agents/lsp-probe-explicit.md declaring:

tools: LSP, Read, ToolSearch, Bash

The dispatched subagent could call Read, Bash, and ToolSearch.
Grep and Glob, correctly not granted because they are absent from the list,
were withheld — so the allowlist mechanism works exactly as documented.
Only the LSP entry silently evaporated:
no warning at dispatch time,
no error until the tool is actually called.
Reproduced on both Opus and Sonnet.

Also worth fixing:
the agent-type roster advertises this agent as (Tools: LSP, Read, ToolSearch, Bash),
which is not what it receives.

A sibling agent with no tools: field at all
received the harness default subagent toolset —
name-for-name identical to plain general-purpose, still no LSP
rather than the dispatching parent's tools.
So there is no frontmatter path to LSP:
neither a narrow allowlist nor omitting the field entirely.

Exhibit C — the error message is wrong and actively misleading

LSP is disabled for this session, in subagents as well as here.
was returned to 5+ independent subagent dispatches across two models.
It describes a session-wide kill switch that does not exist.

This is not a cosmetic complaint:
the first investigator to hit this
reasonably concluded LSP was disabled session-wide,
reported that the parent could not use it either,
and only a direct parent-side test disproved it.
The message sends debuggers in the wrong direction.

Causes ruled out

Each of these was tested and eliminated, so they don't need re-litigating:

  • All local configuration — reproduces in a clean room with a fresh config dir,

a fresh token, one plugin, and no CLAUDE.md / hooks / skills / custom agents.
Note also that no permissions mechanism could produce this shape:
permissions are inherited downward by subagents, never inverted,
and the pruning is visible in the harness-generated deferred registry itself.

  • Plugin install / marketplace config — verified correct (see Environment); server works for the parent.
  • Missing binarypyright-langserver and pyright 1.1.410 both present.
  • Settings — nothing matching lsp|disabledTools|disableAllTools|enableLsp|languageServer

in user, project, or managed settings; no /etc/claude-code/managed-settings.json.

  • Session staleness — a 25-minute-old session reproduces it.
  • Agent type — plain built-in general-purpose and two custom types all fail.
  • tools: frontmatter — see Exhibit B.
  • --allowedTools LSP — headless works with and without it.
  • Model — identical on Opus 5 and Sonnet 5.
  • Permission mode — 8/8 interactive dispatches ABSENT across four modes.
  • Startup mode vs currently-active mode — a mid-session Shift+Tab cycle changes nothing.

The only variable that changes the outcome is interactive vs -p.

Impact

Any workflow that delegates code navigation to subagents loses LSP entirely.
Every dispatched agent silently falls back to grep/find via Bash,
which is slower, costs far more tokens, and is less accurate
than findReferences / goToDefinition / documentSymbol.
Project instructions that tell subagents to prefer LSP over text search
are currently unfollowable, and there is no way to detect this from configuration —
only by dispatching an agent and watching it fail.

Related

  • #27498 — "[BUG] LSP tool is no longer being registered as a callable tool"

(worked in 2.1.34, broke in 2.1.50, ToolSearch returned 0 matches; closed as not planned).
Same family. The main-session half appears fixed since;
the subagent half is what this report covers, with a much tighter reproduction:
parent and subagent differing inside one session.

  • #31002 — built-in system tools moved behind ToolSearch (undocumented).

The mechanism that makes tool availability an indexing problem.

  • #32485 — ToolSearch not indexing things it should surface.
  • #60763 / #61993 / #46424 / #80036 — the Agent/Task tool being pruned from subagents

despite the docs saying it is inherited. Same shape of bug.

Possibly separate issue, noticed while investigating

Happy to split this out if preferred.

Grep and Glob are absent from every agent, parent included.
Parent active set:
Agent, Artifact, AskUserQuestion, Bash, Edit, Read, ReportFindings, ScheduleWakeup,
ShareOnboardingGuide, Skill, ToolSearch, Workflow, Write
— no Grep, no Glob,
and neither in the deferred list.
Their error text ("Grep is not available in this session — search file contents with
grep via the Bash tool instead"
) is at least accurate, unlike LSP's.
If this is intentional, it's undocumented, and it makes
the standard "use Grep/Glob for text search" guidance unexecutable.

View original on GitHub ↗

3 Comments

yohanan · 14 days ago

Cross-reference: ReportFindings shows this same shape, which may make this issue the general mechanism rather than an LSP-specific one.

Measured in an interactive session, same session seconds apart: the main thread had ReportFindings with a full schema and a live call succeeded, while a subagent lacked it from its tool list entirely and a call attempt returned No such tool available. Detail and the exact error string are in #84093 (which is otherwise reported as a /code-review bug). #85230 reports the same pattern for ListMcpResourcesTool / ReadMcpResourceTool in background subagents.

Three tools, same signature — present in the parent, absent in the subagent, interactive sessions. If the pruning you've identified here is the cause, those two are symptoms of it.

aolunavipartner · 2 days ago

Still reproduces on 2.1.250 / Windows 11 with a different LSP plugin, so it is not pyright- or Linux-specific.

Environment: Claude Code 2.1.250 (native win32 binary), ENABLE_LSP_TOOL=1 in ~/.claude/settings.json env, plugin al-language-server-go-windows@claude-code-lsps 1.11.3 (AL / Business Central), interactive session, AL project (~10k .al files).

Parent session: LSP documentSymbol on a table file returns full symbols; workspace diagnostics stream in — the server is connected and healthy.

Subagents, same session, seconds later:

| Agent type | tools: frontmatter | Probe | Result |
|---|---|---|---|
| custom (.claude/agents/*.md) | explicit list including LSP | ToolSearch select:LSP | No matching deferred tools found |
| custom, same | same | asked to call LSP directly and dump its function list | function list = Read, Grep, Glob, Bash, ToolSearch; deferred = MCP tools only; LSP absent from both |
| general-purpose | * | ToolSearch select:LSP | absent |
| Explore | built-in | ToolSearch select:LSP | absent |

Two additional data points beyond the original report:

  1. Ordering doesn't matter. Probed both before any parent LSP call and after the parent had a successful documentSymbol (i.e. after the server's "has ever connected" state was true) — absent both times. So it is not a lazy-connect race.
  2. MCP tools propagate fine in the same subagents (three MCP servers, all reachable via ToolSearch), so it is specific to the built-in LSP tool, not tool propagation in general.

From the bundled JS (2.1.250): the LSP tool is included in the base tool list unconditionally, isEnabled() is the LSP manager's hasEverConnected, and the subagent pool filter's denylist contains only interactive tools (TaskOutput, ExitPlanMode, EnterPlanMode, AskUserQuestion, Poll, Workflow, memory_*, …) — nothing that names LSP. Whatever prunes it happens elsewhere in the subagent pool build.

runvouch · 2 days ago

Workaround that's held up for me while this is open: don't route the subagent through the LSP tool at all, give it the CLI. For Python, pyright --outputjson path/to/file.py from Bash returns the same diagnostics the language server would, as JSON the agent can parse, and subagents keep Bash even when LSP is pruned. It's slower (fresh process each call, a couple of seconds on a mid-size project) and you lose documentSymbol/hover, but for the "find the type errors in the files you just touched" step it's been fine.

The other thing I've done is move the LSP-dependent step into a nested claude -p call from the subagent's Bash, since you've confirmed headless dispatch does get the tool. Ugly, but it's a one-line change and it unblocked a nightly job.