microsoft / microsoft/vscode-remote-release

Remote SSH: server shuts down after reconnect due to hardcoded 5-minute extension host timer triggered by IPC failure

Open
#11,734 1 comment 0 reactions 1 assignee View on GitHub

@alexdima is already working on this.

Since Jul 3, 2026.

Dominant language
Dockerfile
Stars
4.2k
Forks
470
Avg merge
1d 1h
Merged PRs (30d)
1

Description

Environment

Client (local machine):

  • OS: Ubuntu 22.04
  • Kernel: 6.8.0-124-generic (x64)
  • VS Code: 1.127.0 (commit 4fe60c8b, x64)

Server (remote machine):

  • OS: Ubuntu 22.04.5 LTS (Jammy Jellyfish)
  • Kernel: 6.8.0-1060-aws (x86_64, AWS EC2 instance)
  • VS Code Server: reproduced on 1.127.0 (4fe60c8b) and 1.128.0 (fc3def67)
  • Node.js: v24.14.1
  • Connection type: Remote - SSH

Steps to Reproduce

  1. Connect to a remote Linux machine via Remote SSH
  2. Work normally for 30+ minutes (extension host IPC pipe is active)
  3. Disconnect for 60–90 minutes (sleep laptop / lose network)
  4. Reconnect from VS Code client (client may have restarted, generating a new session token)

Expected Behavior

The server spawns a fresh extension host if the old IPC pipe is broken, and the
session resumes normally.

Actual Behavior

The server shuts down completely within ~10 minutes of reconnection. The user must
manually trigger a new connection, which spins up a fresh server. This has occurred
reproducibly 6+ times over one week.

Root Cause

There are two independent 5-minute timers that can each kill the server — either
one is sufficient to trigger a full shutdown.


Timer 1 — Extension host internal timer (extensionHostProcess.js)

When the extension host receives a new client connection (IPC message type===2),
it starts two countdown timers:

let r = fZ("VSCODE_RECONNECTION_GRACE_TIME", 108e5)  // 3 hours
let s = r > 0 ? Math.min(3e5, r) : 0                 // hardcoded 5-min cap
let a = new qh(() => ta("renderer disconnected for too long (1)"), r)
let l = new qh(() => ta("renderer disconnected for too long (2)"), s)

The 3e5 cap (300,000 ms = 5 minutes) is hardcoded. Setting
VSCODE_RECONNECTION_GRACE_TIME does not change it — the second timer is always
capped at 5 minutes regardless. If the renderer does not fully re-establish its
connection within 5 minutes, the extension host calls process.exit(0).


Timer 2 — ManagementConnection short grace time (server-main.js)

In server-main.js, the ManagementConnection class has a second 5-minute timer:

let s = 3e5;  // hardcoded 5 minutes
this._reconnectionShortGraceTime = i > 0 ? Math.min(s, i) : 0

When a new client connects to an existing session (shortenReconnectionGraceTimeIfNecessary()
is called), this timer starts. After 5 minutes it calls _cleanResources(), which
disposes the ManagementConnection and triggers ExtensionHostConnection._cleanResources(),
which calls this._extensionHostProcess.kill() — externally terminating the extension
host process.

This is the timer responsible for the log message:
"Another client has connected, will shorten the wait for reconnection 5m before disposing"

Once the management connection is disposed, the server shuts down 5 minutes later
("all consumers inactive").


Triggering condition: SIGPIPE on reconnect

Both timers are triggered by the underlying SIGPIPE bug: after a long disconnection
(~60–90 min), the extension host closes its IPC handle. When the client reconnects
with a new session token, the server calls process.send() to deliver a new socket
to the old extension host — but the IPC pipe is dead, raising SIGPIPE. The server
does not recover by spawning a fresh extension host. Instead both 5-minute timers
begin their countdown, and whichever fires first triggers a full server shutdown.


Full log sequences (from remoteagent.log, times UTC)

Timer 2 example (ManagementConnection short grace time):

2026-07-09 16:06:26  [error]  Error: Unexpected SIGPIPE
2026-07-09 17:03:16  [info]   [ManagementConnection] Another client has connected,
                               will shorten the wait for reconnection 5m before disposing...
2026-07-09 17:08:16  [info]   [ExtensionHostConnection] <2950> Extension Host Process
                               exited with code: 0, signal: null.
2026-07-09 17:08:18  [info]   [ExtensionHostConnection] <14423> Extension Host Process
                               exited with code: 0, signal: null.
2026-07-09 17:13:18  [info]   ServerLifetime: all consumers inactive, shutting down

Timer 1 example (extension host internal timer):

2026-07-02 15:47:15  [error]  Error: Unexpected SIGPIPE
2026-07-03 00:17:00  [info]   [ManagementConnection] Another client has connected,
                               will shorten the wait for reconnection 5m before disposing...
2026-07-03 00:22:01  [info]   [ExtensionHostConnection] <49772> Extension Host Process
                               exited with code: 0, signal: null.
2026-07-03 00:36:35  [info]   ServerLifetime: all consumers inactive, shutting down

Proposed Fixes

Fix 1 (preferred — root cause): When process.send() raises SIGPIPE, spawn a
fresh extension host instead of abandoning the old one. This eliminates the trigger
condition for both timers.

Fix 2 (mitigation — extensionHostProcess.js): Remove the hardcoded 5-minute cap
in the extension host internal timer — change Math.min(3e5, r) to r. This lets
the extension host wait the full configured grace period before giving up.

Fix 3 (mitigation — server-main.js): Remove the hardcoded 5-minute cap in
ManagementConnection._reconnectionShortGraceTime — change let s = 3e5 to let s = i
(where i is the reconnection grace time). This prevents the management connection
from being disposed prematurely when a new client connects.

Fix 4 (client-side): During rapid reconnect cycles caused by brief network drops,
reuse the same reconnection token so the server does not interpret each retry as a new
client arrival triggering the short grace time.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.