[FEATURE] Pass CLAUDE_CODE_SESSION_ID to plugin MCP server subprocess env (stdio)

Status Fixed / completed
Maintainer reply None cached
Activity 3 comments · opened May 23, 2026 · closed May 27, 2026

Plugin-bundled stdio MCP servers (declared via mcpServers in .claude-plugin/plugin.json) currently receive these env vars at spawn time:

  • CLAUDE_PLUGIN_ROOT
  • CLAUDE_PLUGIN_DATA
  • CLAUDE_PROJECT_DIR

They do not receive CLAUDE_CODE_SESSION_ID, even though that variable already exists in the codebase and is propagated to Bash-tool subprocesses per #25642 (resolved in v2.1.132).

Why this matters

A plugin MCP server that wants to route messages per-session — for example, push events into a specific running session via a localhost sidecar — cannot reliably identify which session spawned it. The plugin sees its own pid, its plugin-data dir, the project dir, but has no way to correlate any of that with the host session whose lifecycle it shares.

Existing related discussion:

  • #41836 — same gap for HTTP-transport MCP servers (Mcp-Session-Id header not echoed, clientInfo is anonymous).
  • #25642 — resolved by adding CLAUDE_CODE_SESSION_ID to Bash-tool subprocess env only.
  • #44607 — closed as duplicate of #25642, no MCP angle.
  • #56879 — documented CLAUDE_CODE_SESSION_ID as Bash-tool-only scope.
  • modelcontextprotocol/modelcontextprotocol#231 — confirms MCP spec doesn't prescribe how a host conveys session identity to a stdio server; this is a host-implementation choice.

Why workarounds don't compose

Tried writing a sibling SessionStart hook that drops the current session_id into ${CLAUDE_PLUGIN_DATA}/active-session.json for the plugin server to read at startup. Fails for concurrent sessions — a Worker spawn races with a Conductor's startup; both hooks write to the same file; the second-spawning plugin server reads the first-spawning session's sid. Result: cross-session message routing failure.

PID-keying (writing active-session-by-pid/<parent-pid>.json) is the next-cleanest fallback but still depends on hook-vs-server-startup ordering and breaks on session-resume.

Proposed fix

Add CLAUDE_CODE_SESSION_ID: <current-session-id> to the env literal already built for plugin stdio MCP server spawn, next to the existing three plugin vars. Single-line addition.

Concrete dispatch-routing breakage this would fix

In a plugin I'm building (orchestrates Conductor/Worker Claude Code sessions via per-session MCP channels), the missing env var means:

  • The Conductor's channel server can't reliably write a sidecar keyed on its own sid (races with Worker startup overwriting the shared hint file).
  • A GUI trying to push /handback <worker-sid> to the Conductor ends up POSTing to the Worker's channel server because the sidecar was written with the wrong sid.
  • The Worker reasonably refuses the misrouted message, and the dispatch loop stalls.

Happy to send a PR if helpful.

View original on GitHub ↗

3 Comments

giruuuuj · 4 months ago

Analysis

The ask is straightforward: add CLAUDE_CODE_SESSION_ID=<current-session-id> to the environment variables passed to plugin stdio MCP servers at spawn time, alongside the already-existing CLAUDE_PLUGIN_ROOT, CLAUDE_PLUGIN_DATA, and CLAUDE_PROJECT_DIR.

Current state

| Env var | Bash tool | Plugin MCP stdio |
|---------|:---------:|:----------------:|
| CLAUDE_CODE_SESSION_ID | Yes (v2.1.132, #25642) | No |
| CLAUDE_PLUGIN_ROOT | N/A | Yes |
| CLAUDE_PLUGIN_DATA | N/A | Yes |
| CLAUDE_PROJECT_DIR | N/A | Yes |

The session ID is already tracked in the host process — it just isn't forwarded to the MCP server's environment block. This is a single-line addition in the env-var assembly code for plugin MCP server spawn.

Why it's needed

Plugin MCP servers that manage per-session state (routing, sidecars, channel servers) need a reliable session identifier. Current workarounds (hook files, PID keying) all race or break under concurrency and session-resume.

No user-side workaround

The env vars are set at spawn time in closed-source CLI code. There's no way for a user to inject CLAUDE_CODE_SESSION_ID into the MCP server environment today.

i2pacg · 4 months ago

Bot-closure was premature — empirically still not fixed in v2.1.152

This issue was auto-closed by ant-github-proxy[bot] on 2026-05-27 as part of a 430-issue mass-closure sweep that day, with no maintainer comment and no linked code-PR (only docs PR #61754, which is still open and only documents the gap). Reopening with empirical evidence the env var is still missing in the latest public build (2.1.152).

Primary-source probe (2026-05-27)

Modified bd-cadence-channel (a plugin Bun stdio MCP server) to expose a debug endpoint dumping its process.env. Ran two probe stages against Claude Code v2.1.152 (latest available via claude install latest):

Stage 1 — env-inheritance check. Spawned a session via wt.exe → claude.exe with --session-id 2b2a0d9c-873d-480a-8c08-4c0811a476f1. The /env-debug response showed CLAUDE_CODE_SESSION_ID: 50feefa3-86ca-4c87-81c4-12eb45463bdd — but that value was the launching shell's own session uuid, not the spawned session's. The env var was inherited from the parent shell, not injected by claude.exe.

Stage 2 — clean-env probe. Re-spawned with CLAUDE_CODE_SESSION_ID stripped from the parent env before launch. Fresh sid 87e5d950-a018-4a2b-b231-454ee20e84f9. /env-debug returned:

{
  "CLAUDE_CODE_SESSION_ID": null,
  "BD_CADENCE_INSTANCE_ID": null,
  "CLAUDE_PLUGIN_ROOT":   "<path>",
  "CLAUDE_PLUGIN_DATA":   "<path>",
  "CLAUDE_PROJECT_DIR":   "<path>"
}

CLAUDE_PLUGIN_ROOT / CLAUDE_PLUGIN_DATA / CLAUDE_PROJECT_DIR are injected as expected. CLAUDE_CODE_SESSION_ID is not.

What the binary shows

String-grep of the 2.1.152 launcher at ~/.local/bin/claude.exe (PE32+ 235MB self-contained bundle) finds exactly four occurrences of CLAUDE_CODE_SESSION_ID:

  1. Allowlist L01 used by Bash-command bare-assignment permission gating (unrelated to MCP spawn).
  2. Allowlist Bq$ used by the same gate.
  3. The Bash-tool env-builder naH(H){let q={CLAUDECODE:"1",CLAUDE_CODE_SESSION_ID:H.sessionId};...} — shipped in v2.1.132 per #25642.
  4. Host-internal process.env.CLAUDE_CODE_SESSION_ID = y8() self-assignment.

The two plugin MCP spawn env-blocks visible in the binary are:

let D = {
  CLAUDE_PLUGIN_ROOT: q.path,
  CLAUDE_PLUGIN_DATA: G8H(q.source),
  ...w.env || {}
};

let z = {
  CLAUDE_PLUGIN_ROOT: q.path,
  CLAUDE_PLUGIN_DATA: G8H(q.source),
  CLAUDE_PROJECT_DIR: r4(),
  ...A.env || {}
};

Neither references CLAUDE_CODE_SESSION_ID. The one-line addition that would close this is shape-identical to what's already done for the Bash tool — read the session id from the same host-side handle, drop it into the env object.

What the bot-closure actually was

ant-github-proxy[bot] closed 430 issues on 2026-05-27, breakdown {completed: 16, duplicate: 12, not_planned: 2} per gh api search/issues?q=repo:anthropics/claude-code+is:closed+closed:2026-05-27. The cohort spans unrelated issues (ConnectionRefused bugs, /context UX, voice-transcription quality). It's a mass-triage sweep, not a per-issue decision. state_reason: completed here means "closed by automation," not "implemented."

Why this still matters

Plugin MCP servers that manage per-session state (channel servers, sidecar routers, per-session caches) need a reliable session identifier. Workarounds we've tried — all eventually broken under concurrency or session-resume:

  • SessionStart hook writes a sidecar → race on concurrent spawns
  • PID-keyed handshake → launcher/runtime fork makes hook-PID ≠ MCP-PID
  • Sibling-by-PPID lookup → PPID changes between hook invocations of the same session (empirically verified)

The pattern we landed in bd-cadence is an env-bridge: GUI sets BD_CADENCE_INSTANCE_ID=<uuid> on the spawn process env alongside --session-id <uuid>. claude.exe inherits and propagates, bun reads at startup. This works because the spawning GUI controls the env block — but it doesn't help any plugin that needs to handle sessions launched outside its own spawner (e.g. a claude --resume <sid> started manually by the operator). The first-party env injection would.

Filing a docs PR alongside this comment that adds the env-bridge pattern as a worked example under examples/ for plugin authors hitting the same wall today.

Could a maintainer reopen and route this to whoever owns the env-assembly code path? Happy to test against any pre-release build that includes the fix.

github-actions[bot] · 2 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.