[控场AI]
· 10 分钟阅读· 5,180 字

让 Claude Code 在关掉终端后继续跑:tmux 与 SSH 方案

让 Claude Code 在关掉终端后继续跑:tmux 与 SSH 方案

用 tmux、远程服务器和 CLI 恢复参数,让 Claude Code 等 Agent 在终端关闭、休眠或崩溃后仍能继续工作。

文章讨论当终端关闭、SSH 断开、笔记本休眠或应用重启时,正在运行的 AI 编码 Agent(如 Claude Code)进程会被中断、进度归零。作者给出三层叠加的解决思路:其一,用 tmux 把进程放进独立于窗口的 server 中,通过 `tmux new -A -s` 命名会话并随时 attach;Claude Code 自身也提供 `claude --bg` 后台会话,但机器重启仍会终止;其二,把 Agent 放到不休眠的远程机器上,用 `ssh -t ... tmux new -A -s` 作为可开关的窗口;其三,进程若已死亡,用 `claude --continue`/`--resume` 和 `codex resume` 恢复对话历史,但无法恢复进行中的工具调用和后台任务。作者还介绍了自家 Hermes IDE 在 macOS/Linux 上的同等能力,并用一张「什么杀死运行 vs 什么让它继续」的表格指导选型,核心原则是给每个长时间会话命名。

关掉终端标签页去整理屏幕,Claude Code 正在进行的三十个文件模块重命名任务就此中断。二十分钟的进度,瞬间归零。SSH 连接在地铁进隧道时断开、终端应用自动更新重启——类似场景每天都在发生。

对话历史通常能保留,正在运行的进程却不行。普通的 claude 或 codex 会话活在启动它的终端里,窗口就是 Agent 的生命维持系统。

解决思路分三层叠加:让进程脱离窗口独立存在,把它放到不休眠的机器上,万一还是挂了就用 CLI 自带参数恢复对话。

tmux:给 Agent 一个不属于窗口的家

tmux 把 shell 跑在一个比终端更长寿的服务器进程里。官方手册写得很直白:每个会话"是持久的,能经受意外断开"。你 detach,Agent 继续跑;你 attach,回到离开时的状态。

启动一个命名会话并在其中启动 Claude Code:


tmux 采用 client-server 架构:第一次运行 `tmux` 时,后台会启动一个 tmux server 进程,所有会话、窗口和窗格都由它托管。终端里看到的界面只是一个 client,通过 Unix socket 连接到 server。这意味着 client 的消失(关闭标签、退出终端应用、SSH 断开)只影响显示,不影响 server 里运行的进程。`Ctrl-b d` 触发的 detach 就是主动断开 client 而保留 server。这也解释了为什么 tmux 会话在同一个用户下是共享的——从任意终端 attach 都是同一份状态。server 本身仍是一个普通用户进程,因此会随机器休眠而暂停、随重启而消失,这正是它能力的边界。

`tmux new -A -s name` 中的 `-A` 等价于「new-session 若目标会话已存在则改为 attach-session」。没有它,重复执行会因名称冲突报错;有了它,同一条命令同时承担「创建」和「重连」两种语义,非常适合写进 shell 别名或历史记录反复调用。配合 `-s` 的命名,你在多条会话之间切换时不需要记 PID,也不需要靠 `tmux ls` 先查再连。
# 创建会话,若已存在则直接接入
tmux new -A -s auth-refactor

# 在 tmux 内:给 Claude 会话也起个名字
claude -n auth-refactor

按 Ctrl-b 然后 d 断开。终端随便关。从任意新终端:

tmux ls                       # 列出会话
tmux attach -t auth-refactor  # 回到离开的地方

-A 这个参数值得记住。它让 new-session "在会话名已存在时等同于 attach-session",同一条命令第一次创建会话、之后每次都重连。放进 shell 历史里,再也不用思考该用哪条命令。

终端窗口关闭而进程在保护罩内继续运行

tmux 有一个局限:笔记本休眠时它也随之暂停,进程要等机器唤醒才恢复。这能应对关闭标签或终端应用崩溃,但应对不了合上盖子还需要继续推进的重构任务。

Claude Code 现在有了自己的方案。claude --bg "finish the auth refactor" 启动一个后台会话并立即返回,打印出会话 ID。独立的 supervisor 进程负责运行它,按文档说法你可以"关闭 shell"而工作继续。会话在睡眠后保留,但"关机或重启机器会停止运行中的后台会话"。用 claude attach <id> 重连,claude logs <id> 查看,claude agents 列出全部。同一页面还有两个注意点:agent view 目前是研究预览版;已完成或等待你输入且无附加进程的会话,大约一小时后会自动停止,不过对话仍保存在磁盘上,下次 attach 或回复时恢复。

把 Agent 放到不休眠的机器上

如果任务要在笔记本合盖时继续,进程必须活在一直开机的机器上:云 VM、家用服务器、桌下的工作站。在上面装 tmux,SSH 就变成一扇可以随手开关的窗户。

ssh -t dev.example.com tmux new -A -s auth-refactor

-t 是必须的,因为 tmux 需要终端。SSH 手册把它描述为"强制分配伪终端",用于在远程机器上运行"基于屏幕的程序"。跑这行命令,在里面启动 claude -n auth-refactor,合上笔记本。地铁进隧道连接断开时,重新跑完全相同的命令。-A 把你放回正在运行的会话。

合盖的笔记本连接着仍在运行、亮着橙灯的服务器

要记住什么跟着 Agent 走。仓库、.env 文件、工具链和 Agent 自身的登录状态都必须存在于远程机器上,因为那是 Agent 读取和运行东西的地方。在你 push 之前,commit 落在那台机器的 checkout 上。

ssh -t 中的 -t 表示强制分配伪终端(pseudo-terminal,pty)。默认情况下,SSH 只有在本地已经是终端时才会申请 pty;一旦把命令作为参数传给 ssh(例如 ssh host tmux ...),远端就会以无终端的方式运行。tmux 作为全屏交互程序必须有终端才能初始化,否则会报 open terminal failed: not a terminal 之类的错误。-t 强制让远端也分配一个 pty,从而让 tmux 正常工作。

由此可延伸出更稳妥的写法:用两次 -t(-tt)保证在本地本无终端时也强制分配,例如从脚本或 CI 中调用时。同时注意 SSH 的 ServerAliveInterval 与 ClientAliveInterval 配置——它们控制心跳与超时断开行为,连接越不稳定越值得调小。无论如何,这些参数只影响连接本身,真正让 Agent 活下来的是远端 tmux server,因为断开 SSH 只是关闭了 client。

进程还是死了:恢复对话

有时候没有任何东西保住进程。机器重启了,或者你忘了用 tmux。对话还在磁盘上。

根据 Claude Code 的会话文档,它"持续将会话保存到本地转录文件",位于 ~/.claude/projects/ 下。两条命令可以恢复:

claude --continue             # 当前文件夹最新的会话
claude --resume auth-refactor # 指定名称的会话

这就是前面用 -n 命名的价值所在。不命名的话,你得从列表里挑。恢复的会话带回完整历史,包括工具调用和结果,以及模型和权限模式。它无法恢复的是正在进行的工作。进程死亡时正在运行的工具"不会在恢复时完成或重新运行"。Claude 看到调用被标记为中断,会先检查它是否已生效再决定是否重跑。后台 Bash 任务同样不会恢复。预期恢复后的第一轮会花点时间检查仓库状态。

Codex 用不同的名字做同样的事:

codex resume --last        # 当前文件夹最新的聊天
codex resume <SESSION_ID>  # 指定会话
codex resume --last --all  # 查找当前文件夹之外

按 Codex 命令参考,--last 跳过选择器,--all 包含"当前工作目录之外的会话"。codex fork 打开同样的选择器,也可以接受 --last 或会话 ID,用于复制对话来尝试不同方案。

把 resume 当作对话的安全网;Agent 本可以在你离开时工作的那二十分钟,无论如何都回不来了。

Claude Code 的会话转录以 JSONL 格式持久化在 ~/.claude/projects/<路径编码>/ 下,每条消息、每次工具调用及其结果都会追加写入。会话标识通常是一个 UUID 或你通过 -n 指定的名称,claude --resume 读取这些文件重建上下文。恢复时,模型看到的历史包括用户消息、助手回复、工具调用与返回,以及当时的模型选择与权限模式,因此它对新一轮对话的判断基本与中断前一致。

但这只是「对话状态」而非「进程状态」。被中断的工具调用在转录中通常带有中断标记,模型需要在下一轮自行判断该工具是否已经产生副作用:例如重命名是否已执行、文件是否已写入。这就是为什么建议先让它检查仓库状态(git status、git diff)再继续,而不是直接让它重跑。后台 Bash 任务因为不在转录中记录 stdout 流和进程句柄,恢复后无法重新接管,只能重新发起。

Hermes IDE 的做法

Hermes IDE 是我在做的项目,以下内容请带着这个前提来看。Hermes 让每个 Agent 跑在自己的真实终端里,它对待应用窗口的方式就像 tmux 对待终端一样。按 README 说法,在 macOS 和 Linux 上"Agent 在 Hermes 退出、更新或崩溃时继续工作,你回到它们离开的地方"。

如果你启用了针对 Claude 的 Agent view,那些会话在应用重启后也会带着之前的对话回到屏幕,Claude 继续推进。

应用窗口淡出后三个小 Agent 仍在各自橙色灯下工作

README 中这种行为明确标注支持 macOS 和 Linux。在 Windows 上,还是按上面的 resume 参数来。

远程工作方面,完整的 New Session 窗口也有 SSH 和 tmux 会话类型,自 2.2.9 起,断开或缓慢的 SSH 连接不再冻结窗口。SSH 支持有独立的说明文档,列出了当前可用与尚不可用的功能,长时间任务前值得先读一遍。

真正稳健的组合还是上一节那套。在 Hermes 中打开 SSH 会话,在远程端运行 tmux new -A -s auth-refactor,在那里启动 Agent。连接断掉时,机器上的 tmux 会话继续运行,同一条命令把你放回去。

按什么会杀死运行来选方案

什么终结了运行什么让它继续
你关闭标签或终端崩溃tmux、claude --bg,或 macOS/Linux 上的 Hermes
笔记本睡眠或离线远程机器上的 tmux,通过 ssh -t
终端应用或 Hermes 更新、退出macOS/Linux 上的 Hermes,或 tmux
机器重启本地什么都救不了,用 claude --resume 或 codex resume

给每个长时间会话起名,tmux new -A -s 命名进程,claude -n 命名对话。两者都只多敲几下键盘,而且都是出问题时你要敲的命令。

选择策略时可以按「进程在哪里、状态在哪里」两个维度来想。进程在哪里,决定它在哪些中断下存活:本地终端 < 本地 tmux < 远程 tmux < 远程 tmux + 持久磁盘。状态在哪里,决定能恢复到什么程度:磁盘上的对话转录能恢复上下文,但进行中的工具调用和后台任务不在其中。最稳的组合是两端都用命名:远程用 tmux new -A -s <name>,对话用 claude -n <name>,并让 claude logs/claude attach 与 tmux attach 构成一套可复现的操作。

另外,把「最小可重入单元」设计小一些也有帮助。让 Agent 的任务拆成能独立完成并提交的若干步,用 git 提交作为检查点,即使进程死了,恢复后也能从最近的提交继续,而不必重建整段进行中的工作。这与 --resume 和 tmux 是正交的,能覆盖机器重启这种谁都救不了进程的场景。

#Claude Code#tmux#SSH#AI编程#终端会话恢复#codex resume#后台运行#开发工具
分享:

相关推荐