Wsl remote连接bug

Issue Description

wsl remote error

Steps to Reproduce

无特殊操作

Expected Behavior

正常remote wsl连接使用

Actual Behavior

wsl连接自己断开

Screenshots / Screen Recordings

Operating System

windows 11

Current Qoder Version (Menu → About Qoder → Copy)

版本: 1.21.2
VSCode 版本: 1.106.3 (system setup)
提交: 7fbe5a663d90672b3e3ceb02e3391a876bc52709
日期: 2026-08-02T11:39:25.623Z
Electron: 42.2.0
Chromium: 148.0.7778.97
Node.js: 24.15.0
V8: 14.8.178.14-electron.0
OS: Windows_NT x64 10.0.26200

Qoder Remote WSL 断联问题分析报告

分析日期:2026-08-03
环境:Windows 25H2 + WSL2 Ubuntu-26.04 + Qoder 1.21.2
Server commit: 7fbe5a663d90672b3e3ceb02e3391a876bc52709


1. 问题现象

Qoder 通过 WSL Remote 连接 Ubuntu-26.04 时,反复出现断联,具体表现为:

  • Server 端日志持续输出 Got delay-shutdown request while in shutdown timeout, delaying
  • 客户端报错 Unknown reconnection token (never seen)
  • 每隔约 60 秒触发一次 delay-shutdown 循环
  • 即使初始连接成功建立,后续仍会断联

2. 环境信息

项目
WSL 内存 48 GB(空闲 46 GB)
CPU 核心数 32 核
系统负载 0.00(几乎为零)
磁盘空间 951 GB 可用
OOM Kill 记录
网络连通性 Test-NetConnection 127.0.0.1:39237TcpTestSucceeded: True

结论:WSL 环境资源充裕,排除性能瓶颈。


3. 关键日志分析

3.1 Server 端日志 (~/.qoder-server/.7fbe5a663d90672b3e3ceb02e3391a876bc52709.log)

Server bound to 127.0.0.1:46063 (IPv4)
Extension host agent listening on 46063

[22:39:07] Extension host agent started.
[22:39:07] [127.0.0.1][ea7b1dc3][ManagementConnection] Unknown reconnection token (never seen).
[22:39:07] [127.0.0.1][feb9b456][ExtensionHostConnection] Unknown reconnection token (never seen).

Server 刚启动即收到带有旧 token 的连接请求,被拒绝。

3.2 客户端 renderer.log 完整时间线

时间 事件 耗时/备注
01:15:53 开始 resolveAuthority -
01:16:33 Server 解析完成,端口 3401 等待 40 秒(server 下载/启动)
01:16:43 Management 连接建立 10 秒
01:16:53 ExtensionHost 连接建立 20 秒
01:17:33 Socket 超时(23 条未确认消息,20 秒无数据) 连接中断
01:17:33 重连到端口 35379(新 server 实例) 重连成功
01:18:05 新连接建立(Management 4ms,ExtHost 48ms) 连接正常
01:19:05 Remote extension host 60 秒未发送 ready 消息 核心错误
01:20:49 窗口重载 -
01:20:50 重连尝试 #1 → 旧 server 仍在运行,端口 35379 使用旧 token
01:21:33 重连尝试 #2 → server 已死,新 server 启动,端口 46063 新 token 生成
01:21:34 客户端发送旧 reconnection token → 被拒绝 Unknown reconnection token
01:21:34 客户端报告 A permanent error occurred 连接彻底失败

3.3 安装脚本日志对比(关键证据)

Attempt #1(01:20:50)— server 还在运行:

Server script is already running    ← 旧 server 存活
listeningOn==35379==
connectionToken==45e67c53-95ef-41a6-b12d-cc9958afd9ab==

Attempt #2(01:21:33)— 43 秒后,server 已死:

Server script already installed     ← 注意:没有 "is already running"
                                    ← 脚本检测到 server 死亡
                                    ← 删除旧 token,启动新 server
listeningOn==46063==                ← 新端口
connectionToken==734d1ecc-938d-4c76-b652-98ba957013c8==  ← 新 token

但客户端的行为:

5/6. sending ConnectionTypeRequest control message.
received error: Connection error: Unknown reconnection token (never seen)

客户端拿到新 server 地址后,没有使用新的 connectionToken 做全新连接,而是使用旧 server 的 reconnection token 尝试"重连"。


4. 根本原因

死循环链路

┌───────────────────────────────────────────────────────────────┐
│                                                               │
│  ① Server 启动(新端口、新 token)                              │
│      ↓                                                        │
│  ② 客户端调用 resolveAuthority 获取新 server 地址               │
│      ↓                                                        │
│  ③ 客户端使用【旧 reconnection token】发起连接(而非新 token)    │
│      ↓                                                        │
│  ④ 新 server 拒绝:Unknown reconnection token                  │
│      ↓                                                        │
│  ⑤ 连接失败,客户端报告 permanent error                         │
│      ↓                                                        │
│  ⑥ Server 无活跃连接 → auto-shutdown 触发                      │
│      ↓                                                        │
│  ⑦ Server 死亡 → 回到 ①                                       │
│                                                               │
└───────────────────────────────────────────────────────────────┘

核心问题

open-remote-wsl 扩展在 server 重启后,没有正确使用新的 connectionToken 建立全新连接,而是错误地走了"重连"路径,使用已失效的 reconnection token。

这是 WebSocket 连接协议层面的 bug:

  • 新连接流程:使用 connectionToken(安装脚本返回的)→ 服务端签发 reconnection token → 后续重连使用此 token
  • 重连流程:使用之前签发的 reconnection token → 仅在同一个 server 实例内有效
  • Bug 表现:server 重启后(新实例),客户端仍尝试用旧 server 签发的 reconnection token 连接

Adding a second confirmed case with the same signature, plus a traced root-cause chain and a verified workaround.

Environment: Windows 11 25H2 (build 26200.9168), WSL 2.6.1.0 / kernel 6.6.87.2, Ubuntu distro, Qoder 1.25.1 (server commit 6e2bf8bcc03df7436277ff8d1f74855c3dbee208). No VPN, no system proxy, no third-party AV (Defender only). WSL resources healthy (15 GB RAM free, 925 GB disk free).

Symptoms: “Connect to WSL” hangs at “Connecting…” indefinitely, or drops ~20-40s after connecting. Persists after wiping ~/.qoder-server and wsl --shutdown.

Diagnosis:

  1. qoder-server inside WSL starts fine, and a raw TCP/HTTP probe from Windows to the server port stayed stable for 45s - so WSL2 localhostForwarding itself is healthy.
  2. Server log shows ManagementConnection + ExtensionHostConnection established, then the renderer reports: “received socket timeout event (unacknowledgedMsgCount: 20, timeSinceOldestUnacknowledgedMsg: 20469)” → WebSocket close 1006 → reconnect loop.
  3. The remote extension host launches but never becomes ready - no remoteexthost log dir is ever created. Attaching to the live process via SIGUSR1 + CDP shows it idle in epoll_wait with handles [Pipe, unix socket, TCP->server port] - blocked waiting for initData from the parent.
  4. Minimal repro: forking out/vs/workbench/api/node/extensionHostProcess standalone with VSCODE_EXTHOST_WILL_SEND_SOCKET=1, the child correctly sends {“type”:“VSCODE_EXTHOST_IPC_READY”} over the fork IPC channel within ~0.2s and waits. The child side is healthy; the server never responds.
  5. In server-main.js, on IPC_READY the handler calls _sendSocketToExtensionHost(), which first does await connectionData.socketDrain() before sending the socket handle + initData. socketDrain() waits for buffered server->client data to flush. If the client-facing transport is stalled, this never resolves - so the extension host waits forever. Deadlock.
  6. Why the transport stalls: every connection was being routed through the fallback bridge - “WSL connection route selected: mode=auto route=bridge reason=latched-fallback”. The bridge forwards each connection with wsl.exe + a bash -c "exec 3<>/dev/tcp/127.0.0.1/<port> && (cat <&3 & cat >&3; wait)" pump inside the distro. This pump stalls server->client under sustained bidirectional traffic, which both triggers the 20s socket timeout and keeps socketDrain() unresolved.
  7. The latched fallback persists across windows/reconnects, so even after the direct path is provably healthy, mode=auto never retries direct.

Verified workaround:
“remote.WSL.bridgeMode”: “never” in User settings.json, then wsl --shutdown and fully restart Qoder. The route becomes direct and the connection completes within seconds.

Suggested fixes:

  • Do not latch the bridge fallback permanently (re-probe the direct path on each new connection, or at least each session).
  • Replace the bash /dev/tcp cat pump with a proper byte forwarder that handles backpressure and close propagation.
  • Add a timeout to socketDrain() before delivering initData to the extension host, so a stalled client socket cannot deadlock the ext-host handshake.

Happy to provide full logs (Remote - WSL channel, renderer.log, ~/.qoder-server/..log) if useful.