VS Code extension leaks claude binary processes on remote-SSH session reconnect
Summary
On a remote-SSH workspace, the Claude Code VS Code extension spawns a new claude binary process every time a session is interacted with after an extension-host reconnect, but never reaps the previous one. Process count grows monotonically with each Developer: Reload Window, network blip, or extension-host restart, eventually destabilizing the SSH tunnel under the combined --debug --debug-to-stderr --verbose --output-format stream-json output flood and forcing further disconnects — feedback loop.
Environment
| Field | Value |
|---|---|
| Extension | anthropic.claude-code 2.1.142 |
| VS Code | 1.120.0 (commit 0958016b2af9, 2026-05-12) |
| Remote OS | Debian 13, kernel 6.12.57+deb13-amd64 |
| Connection | Remote-SSH workspace |
| CLAUDE_AGENT_SDK_VERSION | 0.3.142 |
Evidence
After ~50 minutes of normal use in a single VS Code window, ps -eo pid,rss,etime,cmd | grep '/claude --output-format' shows 7 simultaneous claude processes, all spawned by the same extension host PID:
| PID | RSS (MB) | Etime | Resume ID |
|-----|----------|-------|-----------|
| 6499 | 254 | 02:59 | 87466f9f… (active session) |
| 6642 | 205 | 01:08 | 7f1de88c… |
| 6319 | 203 | 03:23 | d66f53c8… |
| 6705 | 187 | 01:07 | c5b271e5… |
| 6436 | 180 | 03:07 | (no --resume) |
| 6800 | 177 | 00:52 | (no --resume) |
| 6924 | 175 | 00:30 | (no --resume) |
Total ~1.2 GB RSS in claude binaries alone.
ls /home/dev/.vscode-server/data/logs/ shows 10 separate log directories created in the same hour (each = one extension-host restart). The remote-agent log for each starts with:
[error] [<unknown>][…][ManagementConnection] Unknown reconnection token (never seen).
[error] [<unknown>][…][ExtensionHostConnection] Unknown reconnection token (never seen).
— which means the SSH tunnel was torn down rather than gracefully resumed, so the previous extension host never got a chance to clean up its child processes.
Each spawned claude is invoked with the full debug stream:
claude --output-format stream-json --verbose --input-format stream-json
--max-thinking-tokens 31999 --permission-prompt-tool stdio
--resume <session-id>
--setting-sources=user,project,local --permission-mode default
--add-dir <dir>
--debug --debug-to-stderr --enable-auth-status --no-chrome
--replay-user-messages
--debug --debug-to-stderr --verbose are not user-configurable — claude-code-settings.schema.json exposes only 7 settings, none of them debug-related, and extension.js hardcodes the args.
Reproduction
- Connect to a remote-SSH workspace with a flaky-but-stable network (any residential connection where TCP idle disconnects can occur).
- Open a long-running Claude Code chat (a multi-batch refactor task, large context, multiple subagent dispatches).
- Let the extension host reconnect once or twice (either via brief network blip or by
Developer: Reload Window). - After each reconnect, run
pgrep -af 'claude --output-format' | wc -l.
Expected: count = 1 (the active session).
Actual: count grows by 1 on each reconnect; never decreases.
Impact
- Each leaked process: ~200 MB RSS + open handles to its session JSONL (which it
--replay-user-messagesre-parses on every spawn — costly for multi-MB transcripts). - Combined
--debugstderr from N processes saturates the SSH tunnel's send buffer, causing TCP backpressure → SSH session timeout → another reconnect → another spawn. Self-reinforcing. - Symptom from the user side: \"every time I type a message, VS Code disconnects.\" Not actually correlated with the message content; correlated with already being near the saturation threshold.
Hypothesized root cause
The extension's spawn-child-claude path on init / get_claude_state / list_sessions_request does not check for an existing process for the same session ID, and the previous extension host's ChildProcess handles are dropped (not killed) when the host is torn down by a reconnect — leaving the spawned claudes parented to whatever cleanup the OS does (which is nothing, until the parent extension host actually exits cleanly).
Suggested fixes
- Hard reap on extension-host shutdown: the extension's
deactivate()(or equivalent disposable cleanup) shouldprocess.kill()every spawned child before yielding. Currently, on a forced extension-host restart the children are leaked. - De-dupe by session ID at spawn time: before
spawn(claude, ['--resume', sessionId, ...]), walk existing children in this extension host and reject if a live process is already serving that session ID. - Make
--debug --debug-to-stderropt-in: expose aclaudeCode.enableDebugLoggingboolean in the settings schema. Default false. The current always-on debug output is a major bandwidth amplifier when N>1 processes are running over a remote-SSH tunnel — and it's exactly the failure mode where leaked processes hurt most.
Workaround for affected users
Until 1+2 land, periodic cleanup:
# Identify the youngest claude (most likely the active one) and kill the rest:
pgrep -af 'claude --output-format' | sort -k2 -n | head -n -1 | awk '{print $1}' | xargs -r kill
Or simply Developer: Reload Window after every couple of reconnects.
Related
This was diagnosed live in a syncronis-go session — the conversation grew long, the user noticed VS Code dropping on every \"continue\", and a ps walk surfaced the leak.
---
Filed by Claude Opus 4.7 (1M context) on behalf of the user (@timoteicampian). Observations and process-inspection commands were performed inside the same affected session.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗