Cursor SSH 远程连接编辑中途掉线 —— 排查与修复

Cursor 的 SSH Remote 会话在编辑过程中突然断开,丢失 agent 上下文与未保存缓冲区。从保活、网络与远端扩展宿主三个层面定位。

你正在远端开发机上用 SSH Remote 和 Composer 重构代码。重构进行到一半,右下角状态栏突然变红:“Disconnected from <host>“。agent 对话记录消失,一个未保存的缓冲区变灰,重新连接后会拉起一个全新的远程扩展宿主,看不到之前的聊天。这几乎不是 Cursor 本身崩溃,而是底层 SSH 链路死掉了。修复要分三层来做:客户端保活、网络路径稳定性、远端扩展宿主自己的生命周期。按这个顺序处理,掉线就会停。

常见原因

按 SSH Remote 掉线的发生频率排序。

1. SSH 保活间隔太长(或未设置)

NAT 设备、企业代理、云负载均衡器会在 60-300 秒空闲后悄悄回收 TCP 流。如果你的 ServerAliveInterval 没设或者大于 NAT 的超时时间,连接对中间盒来说就是死的,会被清掉。

如何判断:掉线总在安静的空闲段之后(你离开座位、agent 在思考)。一直敲键盘就不会断。~/.ssh/config 中没有 ServerAliveInterval

2. Wi-Fi 漫游或 VPN 切换

笔记本在不同 AP 间漫游、VPN 重连、公司网关重新分配 IP——原有的 TCP 连接已经死了,Cursor 的恢复层无法重新绑定。

如何判断:掉线和走动、休眠/唤醒、tailscale status 显示中继切换在时间上吻合。macOS console 日志里掉线时刻能看到 en0: link state change

3. 远端扩展宿主 OOM

远端的 cursor-server 进程撞到 cgroup 或容器内存上限。宿主进程死掉,SSH 连接还短暂存活,随后 Cursor 报告连接丢失。

如何判断:远端 dmesg | tail 看到 Out of memory: Killed process ... cursor-server。或者 ~/.cursor-server/data/logs/<date>/remoteagent.logFATAL 结尾。

4. 远端 $HOME 磁盘写满

Cursor 在 ~/.cursor-server 下写遥测、日志、语言服务器缓存。磁盘满了之后宿主报错退出,看起来就像掉线。

如何判断:远端 df -h ~ 显示 100% 占用。~/.cursor-server/data/logs/ 体积达到几个 GB。

5. 远端机器睡眠 / 自动伸缩把实例回收了

Spot 实例、临时预览环境、合盖的笔记本,都会无预警终止。从内核角度看 SSH 会话是被正常关闭的。

如何判断:重连后 uptime 显示机器刚重启过。云控制台显示实例被替换或抢占了。

6. 服务端 ClientAliveInterval 与客户端不匹配

如果远端 sshd_config 设置了 ClientAliveInterval 30ClientAliveCountMax 3,那么单向沉默 90 秒后服务端就把客户端踢掉——即使客户端自己以为一切正常。

如何判断:掉线非常规律(总是大约 90 秒或 120 秒空闲后)。远端 /var/log/auth.log 里有 Timeout, client not responding

开始之前

  • 留意掉线是时间相关(总在某分钟数)还是随机:规律的指向超时,随机的指向网络或 OOM。
  • 把状态栏报错原文和时间戳记下来。
  • 在终端用最朴素的 ssh <host> 维持同样空闲量,确认是否也会断;如果会,问题完全在 SSH 传输层,与 Cursor 无关。
  • 改任何东西之前,先记下当前 ~/.ssh/config 的保活设置。

需要收集的信息

  • ssh -v <host> 的前 10 行输出(能看出加载的是哪个 config 与身份)。
  • 开发机对应的 ~/.ssh/config Host 块。
  • 掉线后立刻执行的远端 dmesg | tail -50
  • 远端 df -h ~du -sh ~/.cursor-server
  • 远端 ~/.cursor-server/data/logs/<最新>/remoteagent.log 的尾部。
  • 当前是 Wi-Fi 还是有线、路径上是否经过 VPN/零信任代理。

分步修复

按代价从低到高。

第 1 步:在 ~/.ssh/config 启用客户端保活

新增或修改 Host 块:

Host devbox
  HostName 10.0.0.42
  User you
  ServerAliveInterval 30
  ServerAliveCountMax 6
  TCPKeepAlive yes

ServerAliveInterval 30 每 30 秒发一次保活,远低于常见 NAT 的 60-300 秒空闲超时。ServerAliveCountMax 6 表示连续丢失 6 次(共 3 分钟)才判死,能容忍短暂的 Wi-Fi 抖动。改完后要把 Cursor 的 Remote 窗口关掉重开——SSH 配置是新建连接时读取的,不会热更新。

第 2 步:把日志/缓存挪到 home 之外的卷上

如果 df -h ~ 显示空间紧张:

mkdir -p /var/tmp/cursor-server
ln -snf /var/tmp/cursor-server ~/.cursor-server-logs

然后在 Cursor 设置里把 Remote: Server Data Path 指到大盘上。同时清理旧日志:

find ~/.cursor-server/data/logs -mtime +7 -delete

这一步直接杜绝原因 #4,通常还能省下好几 GB。

第 3 步:提高远端扩展宿主的内存上限

如果 dmesg 显示 OOM kill,说明 cursor-server 的 Node 进程需要更大堆。修改远端的 shell rc:

echo 'export NODE_OPTIONS="--max-old-space-size=4096"' >> ~/.bashrc

然后杀掉正在运行的服务,让它带着新环境变量重启:

pkill -f cursor-server

下次连接时 Cursor 会拉起一个新的远端宿主。仓库超过 10 万文件时,8192 更现实。

第 4 步:服务端加一个与客户端对称的保活

在远端编辑 /etc/ssh/sshd_config(如果 sshd 支持用户级配置目录,也可以用 ~/.ssh/sshd_config.d/):

ClientAliveInterval 30
ClientAliveCountMax 6

重载 sshd:

sudo systemctl reload sshd

两边保活间隔对齐,能避免”服务端不耐烦地踢掉了一个自认为健康的客户端”这种情况。

第 5 步:网络很差时换用更稳的传输

对于不稳的 Wi-Fi 或频繁切换的 VPN,moshtailscale ssh 能挺过 IP 变化:

brew install mosh

Cursor 自己不支持 mosh,但底层连接可以套一层稳定隧道:

tailscale up
ssh -o ProxyCommand="tailscale nc %h %p" devbox

或者直接用 Cursor 自带的 Tunnels 替代裸 SSH——它在 IP 变化时会自动重连。

第 6 步:把远端当作随时可丢的,状态显式持久化

如果开发机是 spot/可抢占实例,重要的东西就别只放在机器上:

# 远端 cron,每 5 分钟把未提交的工作推到 wip 分支
*/5 * * * * cd ~/proj && git add -A && git commit -m "wip" --allow-empty && git push origin HEAD:wip-$(hostname)

配合 Cursor 的 Chats: Persist Remote History,agent 对话会同步到本地账户。这样掉线不再损失任何东西。

验证

  • 打开 Cursor 远程窗口,闲置 10 分钟再回来,状态栏依然绿色。
  • 跑一个故意很长的 agent 任务(大型重构),逐轮观察——中间不再断流。
  • 真实的网络抖动后(手动关 Wi-Fi 5 秒),底栏应在 30 秒内重连,打开的编辑器不丢。

长期预防

  • 团队统一推送一份 ~/.ssh/config 模板,里面预置 ServerAliveInterval 30
  • 开发机至少 8 GB 内存,日志/缓存卷与 $HOME 分开。
  • 每天一个 cron,清理 7 天前的 ~/.cursor-server/data/logs
  • 笔记本到云端的连接优先用 Tailscale 或 Cursor Tunnels,而不是裸 SSH。
  • spot 实例必须有自动 wip 分支推送。
  • 在开发机 bootstrap 脚本里监控 dmesg 与磁盘占用,在机器死掉之前就告警。

常见误区

  • 觉得”少说话更省”,把 ServerAliveInterval 调到 300——多数企业 NAT 在 120 秒就回收空闲流。
  • Cursor 还开着 Remote 窗口时去改 ~/.ssh/config:要等下一次新建连接才生效。
  • 默认 agent 对话保存在服务端:实际默认是本地,远端崩 + 本地缓存过期就丢了。
  • 一边编辑一边 pkill cursor-server:缓冲区会变灰,未保存的修改全部消失。先保存。
  • 在裸终端 ssh devbox 也按相同周期断开,却继续怪 Cursor。
  • 忽视 ~/.cursor-server/data/logs/ 长到几十 GB。

常见问题

Q:Cursor 重连后 agent 把之前的事都忘了,怎么保住聊天?

设置里启用 Chats: Persist Remote History,对话会同步到本地账户,远端宿主重启也能继续。可同时参考Cursor 重启后聊天历史丢失

Q:我改了 SSH 配置但没生效。

Cursor 只在新建远程窗口时读 ~/.ssh/config。“Reload Window”不够,要先”Remote: Close Remote Connection”再重新连接。

Q:在远端开一个常驻 screen/tmux 有用吗?

对 Cursor 本身没用——远端扩展宿主是独立进程,和你的 shell 无关。但远端终端窗口在 tmux 里跑仍然有价值,作为扩展宿主重启时的兜底。

Q:能直接用 mosh 吗?

不能,Cursor 的 Remote-SSH 只走标准 SSH。mosh 用来开一个独立终端;IDE 本身建议走 Tailscale 或 Cursor Tunnels。

相关阅读

标签: #Cursor #排查 #排查 #AI 编程