claude-code - 💡(How to fix) Fix streamable-http MCP sessions never terminated on client shutdown — terminateSession() defined but never called [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#56456Fetched 2026-05-06 06:27:34
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Participants
Timeline (top)
labeled ×3cross-referenced ×1

claude-code never sends DELETE /mcp to terminate streamable-http MCP sessions. Each claude invocation leaks one session, and /exit leaks two. Server-side session maps grow unbounded.

StreamableHTTPClientTransport.terminateSession() exists in the bundled SDK and is the method that sends DELETE. grep -rn "terminateSession" src/ returns zero call sites.

Root Cause

src/services/mcp/client.ts ~L1566:

await client.close()  // closes local transport only — no DELETE sent

client.close() aborts local streams; per the SDK contract it does not terminate server-side state. That requires transport.terminateSession() — which the SDK exposes but claude-code never calls.

Fix Action

Fix

Before client.close() in the cleanup function:

const transport = (client as any)._transport
if (transport?.terminateSession) {
  try { await transport.terminateSession() }
  catch (e) { logMCPDebug(name, `terminateSession failed: ${e}`) }
}

(The _transport access is ugly — you'll have a cleaner internal accessor.)

Code Example

22:13:48  session opened <A>  claude-code/2.1.128 (cli)
22:14:00  /exit pressed
22:14:00  session opened <B>  claude-code/2.1.128 (cli)

---

await client.close()  // closes local transport only — no DELETE sent

---

const transport = (client as any)._transport
if (transport?.terminateSession) {
  try { await transport.terminateSession() }
  catch (e) { logMCPDebug(name, `terminateSession failed: ${e}`) }
}
RAW_BUFFERClick to expand / collapse

Summary

claude-code never sends DELETE /mcp to terminate streamable-http MCP sessions. Each claude invocation leaks one session, and /exit leaks two. Server-side session maps grow unbounded.

StreamableHTTPClientTransport.terminateSession() exists in the bundled SDK and is the method that sends DELETE. grep -rn "terminateSession" src/ returns zero call sites.

Reproducer

Configure any streamable-http server in ~/.claude/settings.json, then claude/exit. Server logs:

22:13:48  session opened <A>  claude-code/2.1.128 (cli)
22:14:00  /exit pressed
22:14:00  session opened <B>  claude-code/2.1.128 (cli)

Neither sends DELETE. <B> does a full initializetools/listresources/list handshake and then the process exits.

Root cause

src/services/mcp/client.ts ~L1566:

await client.close()  // closes local transport only — no DELETE sent

client.close() aborts local streams; per the SDK contract it does not terminate server-side state. That requires transport.terminateSession() — which the SDK exposes but claude-code never calls.

Fix

Before client.close() in the cleanup function:

const transport = (client as any)._transport
if (transport?.terminateSession) {
  try { await transport.terminateSession() }
  catch (e) { logMCPDebug(name, `terminateSession failed: ${e}`) }
}

(The _transport access is ugly — you'll have a cleaner internal accessor.)

Notes

  • The second connect on /exit (<B> above) is observed but not traced; same cleanup path, same fix handles both.
  • Verified against @anthropic-ai/[email protected] and the mirror at github.com/codeaashu/claude-code — zero call sites in both.
  • Codex CLI sends DELETE on shutdown — this is claude-code-specific.

extent analysis

TL;DR

Call transport.terminateSession() before client.close() to properly terminate server-side sessions and prevent session leaks.

Guidance

  • Identify the cleanup function in src/services/mcp/client.ts where client.close() is called and modify it to include the transport.terminateSession() call as shown in the provided fix.
  • Verify that the transport object has a terminateSession method before calling it to avoid potential errors.
  • Test the modified code with the reproducer steps to ensure that the DELETE /mcp request is sent and sessions are properly terminated.
  • Consider refactoring the code to use a cleaner internal accessor for the _transport property instead of using as any.

Example

const transport = (client as any)._transport
if (transport?.terminateSession) {
  try { await transport.terminateSession() }
  catch (e) { logMCPDebug(name, `terminateSession failed: ${e}`) }
}

Notes

The provided fix assumes that the transport object has a terminateSession method. If this method is not available or does not work as expected, further investigation may be needed.

Recommendation

Apply the provided workaround by calling transport.terminateSession() before client.close() to prevent session leaks and ensure proper termination of server-side sessions. This fix is specific to the claude-code implementation and does not affect other components.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix streamable-http MCP sessions never terminated on client shutdown — terminateSession() defined but never called [1 participants]