Bug: LSP tool (jdtls-lsp plugin) not propagated to spawned subagents
[🤖 AI Generated]
Summary
The LSP built-in tool (activated by jdtls-lsp@claude-plugins-official) is not available to spawned subagents — neither in-process (harness-mode) subagents nor tmux team members — even when the plugin is enabled globally in ~/.claude/settings.json.
MCP-server–backed tools (e.g., mcp__glean_default__*, mcp__plugin_context-mode_context-mode__*) propagate correctly to both subagent types. Only enabledPlugins-only built-in tools are affected.
Environment
- Claude Code version:
claude-code_2-1-142_harness(fromAI_AGENTenv var) - Platform: macOS 25.4.0
- Execution context: SDK harness (worktree session)
- Affected subagent backends:
in-processandtmux
Root cause
jdtls-lsp@claude-plugins-official is a bare-marker plugin — its cache directory (~/.claude/plugins/cache/claude-plugins-official/jdtls-lsp/1.0.0/) contains only LICENSE and README.md. There is no .claude-plugin/plugin.json, no .mcp.json, no tool declarations. The LSP tool is a built-in Claude Code feature gated solely on "jdtls-lsp@claude-plugins-official": true in enabledPlugins settings.
The harness builds the --plugin-dir list used when constructing subagent tool environments from plugin cache dirs that have recognized manifest files (.claude-plugin/plugin.json, .mcp.json, .cursor-plugin/plugin.json). Since jdtls-lsp has none of these, it never appears in --plugin-dir. Subagent tool environments are built from --plugin-dir-derived MCP server connections — they do not re-read enabledPlugins to activate built-in tools.
The parent session sees LSP because it reads enabledPlugins at startup and activates the built-in tool directly.
Subagents do not because their tool sets are constructed from --plugin-dir entries only.
jdtls-lsp also does not appear in installed_plugins.json — it is a bare built-in that bypasses the normal install-tracking flow entirely.
Reproduction steps
Prerequisites:
- Install
jdtls-lsp@claude-plugins-officialplugin - Set
"jdtls-lsp@claude-plugins-official": truein~/.claude/settings.json(user-global) and projectsettings.local.json - Confirm the parent session sees LSP:
ToolSearch(query="select:LSP")→ returns full LSP schema ✅
Probe A (non-team, in-process subagent):
Agent({
subagent_type: "general-purpose",
prompt: "Call ToolSearch(query='select:LSP', max_results=1) and report the exact result verbatim."
})
Expected: full LSP tool schema
Actual: No matching deferred tools found
Probe B (team-context, in-process subagent):
TeamCreate({ name: "lsp-probe" })
Agent({
subagent_type: "general-purpose",
team_name: "lsp-probe",
name: "probe",
run_in_background: true,
prompt: "Call ToolSearch(query='select:LSP', max_results=1) and report the exact result verbatim."
})
Expected: full LSP tool schema
Actual: No matching deferred tools found
Both probes confirmed across 6+ spawn attempts in multiple sessions (2026-05-20 and 2026-05-21), covering: fresh teams, varied spawn order, different agent types (general-purpose, java-lsp-worker), tools: frontmatter present/absent, run_in_background true/false.
Configurations exhausted
| Probe | Change tried | Result |
|-------|-------------|--------|
| Global enabledPlugins: true | Set in ~/.claude/settings.json | Parent ✅, subagents ❌ |
| Fresh TeamCreate post-enablement | Created team after global enablement | ❌ |
| Spawn order variation | Enable first, then TeamCreate; vs TeamCreate then enable | ❌ |
| Non-team probe (no team context) | Omit team_name entirely | ❌ |
| tools: ["LSP"] frontmatter | Agent allowlist | ❌ (allowlist, not a grant) |
| Agent tool schema inspection | Check for undocumented extraTools/plugins params | None found |
| claude plugin enable --scope=subagent | Check for propagation scope flag | Flag does not exist |
| Team config editing | ~/.claude/teams/<n>/config.json has no tools/plugins field | No workaround path |
User-accessible workaround attempt (Fix 4 — validation pending)
Other plugins that appear in --plugin-dir (skill-creator, superpowers) all have .claude-plugin/plugin.json. Creating one for jdtls-lsp may cause the harness to include it in --plugin-dir, which in turn may propagate the built-in LSP tool to subagents.
mkdir -p ~/.claude/plugins/cache/claude-plugins-official/jdtls-lsp/1.0.0/.claude-plugin
cat > ~/.claude/plugins/cache/claude-plugins-official/jdtls-lsp/1.0.0/.claude-plugin/plugin.json <<'PLUGINJSON'
{
"name": "jdtls-lsp",
"displayName": "Eclipse JDT.LS (Java LSP)",
"description": "Java language server (Eclipse JDT.LS) for Claude Code, providing code intelligence and refactoring.",
"author": {
"name": "Anthropic",
"email": "support@anthropic.com"
}
}
PLUGINJSON
After creating the file, open a new Claude Code session (the change does not affect the already-running session). Then spawn a subagent and check ToolSearch(query="select:LSP").
Status: file created; fresh-session validation in progress. Will update this issue with results.
Caveat: this file may be overwritten by claude plugin update. It also does not fix the tmux team-member case (separate process, started without --plugin-dir).
Proposed platform fixes
Fix 1 (preferred): When the harness constructs an in-process subagent's tool environment, also read enabledPlugins from settings and activate any built-in tools gated on those plugins — the same logic the parent process runs at startup.
Fix 2: When spawning tmux team members, include --plugin-dir (or an equivalent flag) for every plugin in enabledPlugins, even those with no MCP server files. This lets the spawned process activate built-in tools from enabledPlugins.
Fix 3: Add a subagentPlugins / propagatePlugins settings key that explicitly lists plugins to force into subagent environments:
{
"enabledPlugins": { "jdtls-lsp@claude-plugins-official": true },
"subagentPlugins": ["jdtls-lsp@claude-plugins-official"]
}
Fix 4 (plugin-side): Ship jdtls-lsp with a .claude-plugin/plugin.json so the standard --plugin-dir propagation path carries it to subagents automatically (see workaround above).
Additional notes
- The asymmetry (MCP-server tools propagate, built-in plugin tools don't) is the clearest signal this is a harness propagation gap, not a misconfiguration.
--baremode explicitly skips LSP — but harness-mode subagents are not using--bare. The gap exists despite--setting-sources=user,project,localbeing passed.jdtls-lspis absent frominstalled_plugins.jsonandplugin-catalog-cache.json— it is managed separately from all other plugins, which may be contributing to it falling through the propagation path.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗