摘要:VSCode 的远程终端在日常开发中非常方便,但它有一个长期存在的问题:一旦 VSCode 断开连接,终端中的所有进程都会被强制中止。本文介绍了一种简单但完整的解决方案,只需在 VSCode 的 settings.json 中增加一条配置,就能让每个 VSCode 终端自动对应一个独立的 tmux session。这样,终端进程即使在网络中断、窗口关闭后也能继续运行。相比于手动使用 tmux,这种方法保留了 VSCode 终端的所有特性,包括 shell integration、复制粘贴支持、插件监听以及 code 命令的正常使用。

我们每天都在使用 VSCode 的 Remote SSH 功能来连接远程主机。无论是服务器上的实验、代码调试,还是长时间运行的任务,VSCode 的集成终端都非常方便。但几乎每个人都遇到过一个问题:一旦 VSCode 断开连接,终端里正在运行的进程就会被杀死

也许我们只是网络抖动,或者笔记本合上又重新打开,之前跑了一半的程序就彻底消失了。本文介绍一个简单但非常有效的方案,只需要一行配置,就能让每个 VSCode 终端都自动挂载到一个独立的 tmux session 上。这样即使连接断开,我们也能无缝恢复终端环境,所有命令、进程、输出都完好无损。

为什么需要这个配置

VSCode Remote SSH 插件让我们几乎感觉不到本地与远程的界限。文件、终端、调试器、Git 操作都像本地一样流畅。但它有一个天然的短板:VSCode 终端是依附在 SSH 连接上的。当 VSCode 断线时,终端就会被系统认为没有前台用户,进程自然被清理掉。

一个显而易见的解决办法是使用 tmux。tmux 是一个终端复用器,它让进程独立于 SSH 客户端存在。即使断线,我们也可以通过 tmux attach 重新连上原来的 session。 但直接在 VSCode 里开 tmux 有几个明显的问题:

  • VSCode Shell Integration 无法工作。VSCode 的命令行集成功能依赖于终端本身,而不是 tmux 内部。
  • code 命令失效。VSCode 提供了 code 命令来方便地在命令行中使用 VSCode 打开文件。由于 tmux 中的子进程环境和 VSCode 的 IPC 通信机制隔离,导致我们无法在 tmux 中用 code . 打开文件。
  • 交互体验割裂。tmux 有自己的窗口和分屏管理系统,而 VSCode 使用更加直观,所见即所得的标签页。

其实只需要简单的一条配置(虽然这条配置本身有点复杂),我们就可以实现两全其美的效果:让每个 VSCode 打开的终端背后都对应一个独特的,持久化的 tmux session,使得即使网络断开或者一不小心关闭也可以及时重新自动连接上。最重要的是,这个配置还可以保证 VSCode 终端的大多数集成功能都可以正常继续使用。

核心配置

在 VSCode 的 settings.json 中加入以下内容即可:

"terminal.integrated.profiles.linux": {
    "tmux-tab": {
        "path": "/bin/bash",
        "args": [
            "-c",
            "printf '\\e]633;A\\a\\e]633;B\\a\\e]633;E;tmux new-session -s vsct-$$ -c \"%s\"\\a\\e]633;C\\a' \"$PWD\"; vars=(TERM_PROGRAM TERM_PROGRAM_VERSION VSCODE_IPC_HOOK_CLI VSCODE_IPC_HOOK VSCODE_PID VSCODE_CWD VSCODE_NLS_CONFIG VSCODE_GIT_IPC_HANDLE VSCODE_INJECTION VSCODE_SHELL_INTEGRATION); eargs=(); for v in \"${vars[@]}\"; do eargs+=( -e \"$v=${!v}\" ); done; tmux new-session -d -s vsct-$$ -c \"$PWD\" \"${eargs[@]}\"; tmux set -t vsct-$$ status off; exec tmux attach -t vsct-$$"
        ]
    }
},

这段配置会在你每次新建 tmux-tab 终端标签页时,后台创建一个独立的 tmux 会话 vsct-<bash pid>,然后自动附着进去。这段命令的核心很简单,但是在他的前后有许多修饰命令和预处理过程来让 VSCode 对终端的集成正常工作。 让我们来拆开它一行一行解读:

printf '\e]633;A\a\e]633;B\a\e]633;E;
tmux new-session -s vsct-$$ -c "%s"\a\e]633;C\a' "$PWD";
 
vars=(TERM_PROGRAM TERM_PROGRAM_VERSION VSCODE_IPC_HOOK_CLI VSCODE_IPC_HOOK VSCODE_PID VSCODE_CWD VSCODE_NLS_CONFIG VSCODE_GIT_IPC_HANDLE VSCODE_INJECTION VSCODE_SHELL_INTEGRATION);
 
eargs=();
for v in "${vars[@]}"; do
    eargs+=( -e "$v=${!v}" );
done;
 
tmux new-session -d -s vsct-$$ -c "$PWD" "${eargs[@]}";
tmux set -t vsct-$$ status off;
exec tmux attach -t vsct-$$
 

第一段:向 VSCode 发送 shell integration 信号。

printf '\e]633;A\a\e]633;B\a\e]633;E;tmux new-session -s vsct-$$ -c "%s"\a\e]633;C\a' "$PWD"

这会发送 VSCode 自定义的 OSC 633 序列,按顺序表示:提示符开始(A)、提示符结束(B)、显式设置 ” 要执行的命令行 “(E),以及 ” 准备执行 “(C)。我们把 ” 命令行 ” 写成 tmux new-session -s vsct-$$ -c "<当前目录>",等于告诉 VSCode:接下来要进入 tmux,并且当前目录是什么。VSCode 由此能继续显示命令装饰、记录目录、支持 ” 转到最近目录 ” 等功能。这段输入序列提供了 VSCode shell integration.

第二段:准备要传入 tmux 的环境变量。

vars=(TERM_PROGRAM VSCODE_SHELL_INTEGRATION)
eargs=()
 for v in "${vars[@]}"; 
 do eargs+=( -e "$v=${!v}" ); 
 done;

tmux 的 new-session 支持 -e name=value 为新会话设置环境变量。我们把 VSCode 放进终端的关键变量传入新的 session,比如 VSCODE_IPC_HOOK_CLIVSCODE_CWD 等。传递这些变量解决了 tmux 里无法使用 code 命令的问题:tmux 里使用 code <path> 仍能把文件交回这次的 VSCode 客户端,因为 code 会通过 VSCODE_IPC_HOOK_CLI 的 socket 与 VSCode 对话。

第三段:创建并附着到独立的 tmux 会话。

tmux new-session -d -s vsct-$$ -c "$PWD" "${eargs[@]}";
tmux set -t vsct-$$ status off; 
exec tmux attach -t vsct-$$

这部分命令用 -d 先在后台创建会话,再 attach 进去;-c "$PWD" 让会话从当前目录启动;-s vsct-$$ 用 bash 的进程号确保会话名在每个 VSCode 标签页里唯一;status off 则关闭 tmux 自己的状态栏让界面更加简洁。

经过这一组命令,每次在 VSCode 打开一个新的终端时,它都会自动对应一个独立的 tmux session,完全透明,VSCode 的所有集成功能依然可用。即使断开连接或关闭 VSCode,下次重新打开时,新的终端也会自动重连到同名 session。

让体验更完美

这个配置已经可以正常工作,但我们还可以让它更优雅、更贴近日常使用。

设为默认 Profile

"terminal.integrated.defaultProfile.linux": "tmux-tab",
"terminal.integrated.automationProfile.linux": {
    "path": "/bin/bash",
    "args": ["-lc", "exec $SHELL -l"]
},

这样手动打开的终端都使用 tmux-tab,而自动化任务(如调试器)则用普通 shell,避免干扰。

调整 Tmux 配置

把下面这些加到远程主机的 ~/.tmux.conf。目标是让 tmux 在 VSCode 里尽量不突兀,并确保高级功能能透传到外层终端。

# ~/.tmux.conf
 
setw -g automatic-rename on
set -g allow-rename on
set -g mouse on
set -g allow-passthrough on      # 允许转义序列直达外层终端,tmux 3.3+ 推荐
set -g default-terminal "tmux-256color"
 
# 剪贴板:让 tmux 通过终端的 OSC 52 机制与系统剪贴板对接
set -g set-clipboard on          # 由终端支持决定是否生效
# 如果你安装了能发出 OSC 52 的小工具(如 osc、tty-copy),还可以统一复制出口:
# set -s copy-command 'osc copy'   # 或者 'tty-copy'
 
# VSCode 里无需“鼠标选中即复制”,交由外层 VSCode 处理更自然
set -g @yank_with_mouse off

allow-passthrough on 让图形协议或剪贴板等序列在 tmux 中 ” 直通 ” 外层终端,现代版本 tmux 均支持;set-clipboard on 与 OSC 52(拷贝到系统剪贴板的转义序列)配合,可以在远程 tmux 里复制文字到本地剪贴板;而 copy-command 是 tmux 3.2 引入的统一复制出口,可以指定为外部工具(如 xsel -ixclip 或前述的 OSC 52 工具)。是否生效取决于外层终端对 OSC 52 的支持。

自动清理空闲 Session

随着使用次数增加,可能会积累大量没有被附加的 vsct-* session。可以定期运行以下脚本,自动清理那些没有活动进程的会话:

# Prune detached vsct* tmux sessions without active foreground jobs (zsh)
prune_vsct_tmux() {
  if ! command -v tmux >/dev/null 2>&1; then
    echo "tmux command not found" >&2
    return 1
  fi
 
  local sessions
  sessions=$(tmux list-sessions -F "#{session_name}::#{session_attached}" 2>/dev/null) || return 0
 
  local -a idle=(fish bash zsh sh)
  local killed=0
 
  local line name attached panes pane_cmd lower has_active in_idle
  setopt localoptions shwordsplit
 
  for line in ${(f)sessions}; do
    name=${line%%::*}
    attached=${line##*::}
 
    [[ $name == vsct* ]] || continue
    [[ -n "$attached" && $attached -gt 0 ]] && continue
 
    panes=$(tmux list-panes -t "$name" -F "#{pane_current_command}" 2>/dev/null) || continue
    has_active=0
 
    for pane_cmd in ${(f)panes}; do
      [[ -z "$pane_cmd" ]] && continue
      lower=${pane_cmd:l}
 
      in_idle=0
      for _c in $idle; do
        [[ "$lower" == "$_c" ]] && in_idle=1 && break
      done
 
      if [[ $in_idle -eq 0 ]]; then
        has_active=1
        break
      fi
    done
 
    if [[ $has_active -eq 0 ]]; then
      tmux kill-session -t "$name" && ((killed++))
    fi
  done
}
 

这段脚本会检测所有以 vsct 开头的 session,如果没有附加客户端且里面也没有正在运行的程序,就会自动清理。

总结

通过这段配置,我们可以让 VSCode 的远程终端具备 持久化、透明化、自动化 的特性:

  • 每个 VSCode 终端自动绑定一个独立 tmux session。
  • 即使断线或关闭 VSCode,session 内的进程依然继续运行。
  • 所有 VSCode 集成功能(如 shell integration、复制粘贴、code 命令)依然可用。
  • 可选的脚本与配置让体验更自然、更整洁。