我日常的开发环境是 VSCode Remote SSH 连到一台 Linux 开发机,然后在 VSCode 的集成终端里开 tmux。Claude Code 在 VSCode 终端里直接运行时,能正常连接 IDE——可以读到我选中的代码、跳转文件。但一旦进入 tmux,状态栏就显示「Visual Studio Code disconnected」。
这篇文章记录了从现象到根因的完整排查过程。涉及 Claude Code 的 IDE 发现机制源码、tmux 环境变量继承的坑,以及最终的修复方案。
表面现象和第一个错误方向
最初的怀疑是环境变量 TERM_PROGRAM。tmux 启动子 shell 时会把这个变量覆写为 tmux,而 Claude Code 可能依赖它来判断自己是否运行在 VSCode 终端里。
所以我在 .bashrc 里加了逻辑:检测到 tmux session 中有 VSCODE_IPC_HOOK_CLI 时,就强制 export TERM_PROGRAM=vscode。
试了一圈,没用。TERM_PROGRAM 改对了,IPC socket 也活着,但 Claude Code 仍然报 disconnected。
第二个错误方向:$TMUX 变量
接下来怀疑 Claude Code 检测到 $TMUX 环境变量就直接拒绝连接。试了 TMUX= claude 启动——清掉 $TMUX 后再跑,还是不行。
找到源码,看真正的逻辑
Claude Code 曾经短暂开源过。从源码的 utils/ide.ts 里,我找到了 IDE 检测的完整链路:
1 | export async function detectIDEs(includeInvalid: boolean): Promise<DetectedIDEInfo[]> { |
整个发现机制是这样的:
- VSCode 的 Claude Code 扩展启动后,在
~/.claude/ide/下创建一个{port}.lock文件,内容是 JSON,包含workspaceFolders、pid、transport等信息。 - CLI 端的 Claude Code 启动时,扫描所有 lockfile,尝试匹配。
- 匹配的优先级:先看环境变量
CLAUDE_CODE_SSE_PORT是否指向某个 lockfile 的端口;如果没有,就用 cwd 去匹配 lockfile 里的workspaceFolders。
第一个真正的问题:过期的 CLAUDE_CODE_SSE_PORT
VSCode 在启动集成终端时,会注入一个 CLAUDE_CODE_SSE_PORT 环境变量,指向当前窗口对应的扩展端口。问题是:进入 tmux 后,这个变量被「冻结」在了创建 session 时的值。如果中间 VSCode 重启过、端口变了,tmux 里的变量就指向一个不存在的 lockfile。
验证一下:
1 | $ echo $CLAUDE_CODE_SSE_PORT |
端口 30322 已经不存在了,但环境变量还指着它。Claude Code 拿这个端口去匹配,找不到对应的 lockfile,自然连不上。
第二个真正的问题:祖先进程检查
清掉过期的 CLAUDE_CODE_SSE_PORT 之后,按理说 Claude Code 应该回退到 cwd 匹配模式。但它仍然找不到 IDE。继续看源码:
1 | const needsAncestryCheck = getPlatform() !== 'wsl' && isSupportedTerminal() |
关键在 isSupportedTerminal()。这个函数检查 env.terminal 是否是已知的 IDE 类型(vscode、cursor、windsurf 等)。因为我前面把 TERM_PROGRAM 强制设成了 vscode,所以 isSupportedTerminal() 返回 true,触发了祖先进程检查。
祖先进程检查做的事情是:确认 lockfile 里记录的 IDE pid 是当前 Claude Code 进程的某个祖先。在 VSCode 集成终端里直接运行时,进程树是 VSCode(node) → shell → claude,检查能通过。但在 tmux 里:
1 | tmux: server (pid 1746497) |
IDE 的 pid(1845751)完全不在这条祖先链上。检查失败,lockfile 被跳过。
根因总结
两个问题叠加:
- 过期的
CLAUDE_CODE_SSE_PORT:tmux 继承了旧的端口号,lockfile 已不存在。 - 错误地伪装
TERM_PROGRAM=vscode:触发了本不该触发的祖先进程检查,而这个检查在 tmux 里必然失败。
修复
最终的 .bashrc 配置:
1 | # tmux + VSCode: auto-sync IPC socket so Claude Code can connect to VSCode |
核心思路:
- 不伪装
TERM_PROGRAM。让isSupportedTerminal()返回 false,跳过祖先进程检查。 - 清理过期的
CLAUDE_CODE_SSE_PORT。当端口对应的 lockfile 不存在时 unset 它,让 Claude Code 回退到纯 cwd 匹配模式。 - 保持
VSCODE_IPC_HOOK_CLI的同步。这个变量用于其他 VSCode CLI 功能(比如code命令),继续从 tmux session 环境里刷新。
修复后,在 tmux 里启动 Claude Code,用 /ide 命令手动触发一次连接,显示「Connected to Visual Studio Code.」——之后就能正常读取 VSCode 中打开的文件和选中内容了。
小结
这个问题的排查过程其实挺典型的:表面上看是「环境变量没传进去」,实际上是多个机制交织——lockfile 发现、端口匹配、进程树校验——任何一环出问题都会导致连接失败,而且错误提示只有一个笼统的「disconnected」。
如果没有源码,我大概只能靠猜和试。有了源码之后,三个检查条件一目了然:端口匹配、cwd 匹配、祖先进程匹配。然后反推出「在 tmux 里,不应该伪装成 IDE 的内置终端,而应该走纯 workspace 路径匹配」这个结论。
对于同样在 tmux 里使用 Claude Code 的人,记住两点就够了:别设 TERM_PROGRAM=vscode,以及确保不要残留一个过期的 CLAUDE_CODE_SSE_PORT。剩下的交给 lockfile 的 cwd 匹配机制就好。