跳到主内容
快讯直播
AI智模界
教程

用 tmux 给 Claude Code 开多路并行会话

这篇能做出什么

做完之后,你会有一个叫 agents 的 tmux 会话:里面固定有几路 Claude Code 各自在独立的 git worktree 里干活,一路一个任务域。你用一条命令把任务文件“派”给某一路,用另一条命令一次性扫出所有路的最近输出,所有终端输出同时落盘成日志文件。关掉终端、断网、第二天回来接着看,活还在跑(只要机器没重启)。

具体一点,最终你会拥有:

  • up.sh:一条命令拉起 4 路(数量可改)并行的编码会话,每路一个分支、一个目录、一份日志。
  • dispatch.sh:把 tasks/03-export-csv.md 这类任务卡派给指定的一路,而不是手打一长串 prompt。
  • peek.sh:一条命令打印所有路最近 15 行输出,相当于一个粗糙但够用的监控面板。
  • logs/alpha.log 这类文件:每路的完整终端留痕,可以 grep、可以归档。
  • 一套判断标准:什么时候该 /compact,什么时候该 Ctrl-C 重开,什么时候该把一路拆成两路。

关键思路只有一句:把上下文外置到文件里,tmux 只负责“多个终端 + 可寻址的输入输出”这件事。任务卡是上下文,日志是记忆,会话本身随时可以牺牲掉重开。

前置条件清单

动手前确认这些:

  • macOS 或 Linux,装了 tmux(tmux -V 有输出即可;参数细节以官方手册当前版本为准)。
  • 已安装并登录 Claude Code CLI,终端里直接敲 claude 能进交互界面。版本差异较大,涉及具体命令行参数时以官方文档当前版本为准。
  • 一个 git 仓库,并且工作区是干净的(git status 无未提交改动)。并行会话靠 worktree 隔离,脏工作区会让 git worktree add 的基线变模糊。
  • 仓库根目录能跑通测试命令(npm test、pytest、go test ./... 之类),后面任务卡的“完成标准”要写它。
  • 会用三个 tmux 动作就够:Ctrl-b d 脱离、tmux attach -t agents 回来、Ctrl-b [ 进复制模式滚屏。
  • 建议开 set -g mouse on,鼠标滚轮直接翻历史,省很多事。

目录约定(后面脚本都用这套,想改就改脚本里的变量):

```text

~/work/myproj/ # 主仓库

~/work/wt-alpha/ # agent alpha 的 worktree

~/work/wt-beta/ # agent beta 的 worktree

~/work/logs/ # 各路日志

~/work/tasks/ # 任务卡

~/work/bin/ # up.sh / dispatch.sh / peek.sh

```

分步骤

第 1 步:先把单路跑通,再谈并行

别一上来就写脚本。先手动来一遍:

```bash

tmux new-session -s smoke -c ~/work/myproj

在 tmux 里

claude

```

在里面让它读一个文件、改一行、跑一次测试。确认三件事:TUI 显示正常不折行、能正常滚屏回看、Ctrl-b d 之后 tmux attach -t smoke 能回来。顺手留意一下它“空闲等待输入”时界面长什么样——第 5 步的脚本要靠这个特征判断它忙不忙。

确认没问题后 tmux kill-session -t smoke。

小技巧:如果 TUI 在 tmux 里显示错乱,先确认不是窗口太窄导致的折行,用 tmux set -g default-size 200x50 或 resize-window -x 200 -y 50 调宽(不同 tmux 版本参数略有差异,tmux -V 后查官方手册)。

第 2 步:一路一个 worktree

多路并行最常见的失败不是智能体不够聪明,是两路同时改了同一个文件,合并时互相打脸。用 worktree 把分支和目录一起隔离:

```bash

cd ~/work/myproj

git worktree add -b agent/alpha ../wt-alpha

git worktree add -b agent/beta ../wt-beta

git worktree add -b agent/gamma ../wt-gamma

git worktree add -b agent/delta ../wt-delta

git worktree list

```

规矩定死:alpha 只在 wt-alpha 里改,beta 只在 wt-beta 里改,谁也不许跨目录。合并时一路一路来:git checkout main && git merge agent/alpha。

注意 worktree 共享同一个 .git 目录,所以不要让两路同时往同一个分支提交,也不要在两路里同时跑会改写索引的 git 操作。

第 3 步:写任务卡,而不是写 prompt

send-keys 最怕的就是把几百字的 prompt 塞进终端——多行文本会被 tmux 逐行“回车”,变成一堆半截命令。正确做法是把任务写成文件,终端里只发一句“去读那个文件”。

tasks/03-export-csv.md 模板:

```markdown

任务:给订单导出加编码选项

背景

  • 相关代码:src/export/、src/order/
  • 禁止修改:src/core/(其他 agent 正在改)

要求

1. 导出接口新增 encoding 参数,默认 utf-8,保持向后兼容

2. 补一条单测覆盖 gbk 分支

完成标准

  • [ ] npm test -- export 全绿
  • [ ] 未修改 src/core/

交付

在仓库根目录写 HANDOFF.md:改了哪些文件、遗留问题、下一步建议。

```

最后那个 HANDOFF.md 很关键。它让“这一路的上下文”在会话被清空后依然存在,是后面规避上下文上限的基础。

第 4 步:一键拉起多路会话

~/work/bin/up.sh:

```bash

#!/usr/bin/env bash

一键拉起多路并行 Claude Code 会话

set -euo pipefail

SESSION=agents

REPO_ROOT="$HOME/work/myproj"

LOG_DIR="$HOME/work/logs"

AGENTS=(alpha beta gamma delta)

mkdir -p "$LOG_DIR"

已有同名会话就先清掉,避免 target 重名

tmux kill-session -t "$SESSION" 2>/dev/null || true

0 号窗口留给监控脚本

tmux new-session -d -s "$SESSION" -n monitor -c "$REPO_ROOT"

for name in "${AGENTS[@]}"; do

wt="$HOME/work/wt-$name"

if [[ ! -d "$wt" ]]; then

git -C "$REPO_ROOT" worktree add -b "agent/$name" "$wt"

fi

tmux new-window -t "$SESSION" -n "$name" -c "$wt"

所有输出同时落盘;>> 追加写入,重复执行也不会丢历史

tmux pipe-pane -o -t "$SESSION:$name" \

"sed -u 's/\x1b\[[0-9;]*m//g' >> $LOG_DIR/$name.log"

-l 表示后面是字面量,避免 tmux 解释特殊字符

tmux send-keys -t "$SESSION:$name" -l "claude"

tmux send-keys -t "$SESSION:$name" Enter

sleep 3 # 等 TUI 起来,见常见坑第 5 条

done

tmux select-window -t "$SESSION:monitor"

tmux attach -t "$SESSION"

```

执行 chmod +x ~/work/bin/up.sh && ~/work/bin/up.sh。

几个细节值得解释:

  • sed -u 是去掉终端颜色控制码,让日志用 grep、less 看得下去;-u 保证不缓冲,实时写入。想看原始带色输出就把 sed 去掉。
  • pipe-pane -o 是“开关式”参数:管道已存在时会关闭它。所以重复跑 up.sh 前先 kill 会话,逻辑最干净。
  • 窗口名不要互为前缀(比如 a 和 ab),老版本 tmux 匹配窗口名是前缀匹配,会打错窗口。实在想用短名,目标写成 "$SESSION:=$name" 做精确匹配。

第一次在新目录里跑,Claude Code 可能会问“是否信任此目录”之类的确认。所以建议先手动把每个窗口过一遍首启确认,等 up.sh 全自动时就不会卡在确认框上。

第 5 步:send-keys 派活脚本

~/work/bin/dispatch.sh:

```bash

#!/usr/bin/env bash

用法: ./dispatch.sh alpha tasks/03-export-csv.md

set -euo pipefail

SESSION="${SESSION:-agents}"

TARGET="$1"

TASK_FILE="$2"

if [[ ! -f "$TASK_FILE" ]]; then

echo "任务文件不存在: $TASK_FILE" >&2

exit 1

fi

1) 确认目标窗口存在

if ! tmux list-windows -t "$SESSION" -F '#{window_name}' | grep -qx "$TARGET"; then

echo "没有这个窗口: $TARGET" >&2

tmux list-windows -t "$SESSION" -F '#{window_index} #{window_name}' >&2

exit 1

fi

2) 确认它空闲:最后几行里没有“正在运行”的标志

这里的匹配串要按你实际看到的界面改,先手动观察一次

recent=$(tmux capture-pane -pJ -t "$SESSION:$TARGET" -S -6 | sed '/^$/d')

3) 派活:只发一句短指令,让它自己读文件

MSG="读 $TASK_FILE,按里面的要求执行。完成后写 HANDOFF.md。"

tmux send-keys -t "$SESSION:$TARGET" -l "$MSG"

tmux send-keys -t "$SESSION:$TARGET" Enter

echo "已派发给 $TARGET: $MSG"

```

用法:

```bash

cd ~/work

./bin/dispatch.sh alpha tasks/03-export-csv.md

./bin/dispatch.sh beta tasks/04-fix-pagination.md

```

为什么坚持“只发一句短指令”:send-keys 的输入是终端按键流,不是 API 调用。带换行的长文本会被当成多次回车,特殊字符(;、$、!)在某些 shell 配置下还会被处理。加 -l 只能保证字面量,保证不了多行语义。任务卡 + 一句话,是最稳的组合。

第 6 步:输出留痕与回看

三种回看方式,按粒度选:

```bash

1) 实时盯某一路(跟看直播)

tmux attach -t agents \; select-window -t agents:alpha

2) 抓快照:含历史缓冲区的最后 200 行

tmux capture-pane -pJ -t agents:alpha -S -200 | tail -n 60

3) 翻归档日志:完整、可 grep、可对比

tail -n 80 ~/work/logs/alpha.log

grep -n "error\|Error\|FAIL" ~/work/logs/*.log

```

capture-pane 的参数值得记牢:-p 打到标准输出,-S -200 从历史缓冲区往上 200 行开始取(-S - 是取全部历史),-J 把被窗口宽度折断的行拼回一行——不加 -J,日志里全是硬折行的碎句子。

~/work/bin/peek.sh 一次性扫全部:

```bash

#!/usr/bin/env bash

一眼看完所有路在干什么

set -euo pipefail

SESSION="${SESSION:-agents}"

for w in $(tmux list-windows -t "$SESSION" -F '#{window_name}'); do

echo "===== $w ====="

tmux capture-pane -pJ -t "$SESSION:$w" -S -18 | sed '/^[[:space:]]*$/d' | tail -n 12

echo

done

```

把它挂到 monitor 窗口里循环跑,就是一个极简看板:

```bash

tmux send-keys -t agents:monitor -l \

"while true; do clear; ~/work/bin/peek.sh; sleep 20; done"

tmux send-keys -t agents:monitor Enter

```

第 7 步:上下文上限规避——什么时候该拆

每一路会话都有独立的AI 词典:上下文窗口">上下文窗口,互不共享。这意味着两件事:并行确实能扩大总吞吐;但任何一路自己聊久了,仍然会撞上限。撞上限的典型症状是:

  • 开始重复问同样的问题,或反复读已经读过的文件;
  • 忘记任务卡里写死的约束(“不要改 src/core”),开始越界改;
  • 回答变得笼统,不再引用具体文件路径和函数名;
  • /compact 之后反而更差,因为它把细节压掉了。

对应的处置顺序,从轻到重:

1. 同一任务内的中期整理:任务做到一半,让它把当前进展写进 HANDOFF.md,然后 /compact 压缩对话(命令名以官方文档当前版本为准)。适合“还是一个任务,只是聊太长了”。

2. 换任务就清空:这一路的下一个任务跟上一个无关,直接 /clear 或 Ctrl-C 后重开 claude。别在一个会话里攒三个不相关的任务。

3. 拆会话:出现下面任一情况,就把一路拆成两路、两个 worktree、两张任务卡——

  • 任务要跨三个以上互不相干的目录;
  • 需要同时保留的决策点多到一屏写不下;
  • 你发现自己在对话里反复重申同一批背景(说明背景该进任务卡了)。

拆的时候不需要“迁移上下文”,只需要把已知信息写进新的任务卡,然后 ./bin/dispatch.sh epsilon tasks/05-xxx.md。这就是把上下文外置的好处:会话是可抛弃的。

反方向的判断同样重要:并行路数不是越多越好。四路同时跑测试,本机 CPU 和磁盘先扛不住;同一个账号的请求频率也可能撞限额,表现是一路开始报错、重试、卡住。看到这种信号就减到两三路,或者错峰派活。

第 8 步:收工与合并

每路跑完,先看 HANDOFF.md,再合并:

```bash

cd ~/work/myproj

git checkout main

git merge --no-ff agent/alpha

npm test

```

一次只合一路,合完跑测试,绿了再合下一路。四路一起合,出问题你不知道是谁的锅。

常见坑与排错

1. 多行 prompt 被拆成多条命令。 表现是终端里出现一堆半截指令。原因:send-keys 发的是按键流,文本里的换行等于回车。解法:任务写文件,终端只发一句话。

2. 忘了发 Enter。 加了 -l 之后,文本只是“打进去”,不会提交。必须单独一句 tmux send-keys -t <target> Enter。这两句要连着写,中间不要插别的。

3. 特殊字符被解释。 文本里带 ;、$、!、反引号时,命令变成了别的东西。一律加 -l;确实要发控制键时用键名(Enter、Escape、C-c),不要用字符串模拟。

4. 智能体正在跑,你插话插歪了。 它正在执行时发指令,输入可能被当成排队消息,也可能直接打断。稳妥做法:先 peek.sh 看一眼,要打断就先发 Escape 或 C-c,等回到空闲提示符再派活。

5. 脚本发 claude 时 TUI 还没起来。 表现是命令字母被吞、或落到 shell 提示符上。解法:sleep 3 起步,或者写个轮询函数,反复 capture-pane 直到看见空闲提示符特征再返回。这个特征每个版本界面不同,先手动观察一次。

6. 回看时行数不够 / 全是碎行。 用 -S -200 甚至 -S - 取历史,加 -J 拼回折行。注意 capture-pane 只能拿到 tmux 的 scrollback 缓冲区,超出部分靠 pipe-pane 的日志文件补。

7. 日志文件不更新或重复。 pipe-pane -o 是开关语义,重复执行可能把管道关掉。排查:tmux display-message -p -t agents:alpha '#{pane_pipe}' 看是否为 1。想重开就先 kill 会话再跑 up.sh。日志用 >> 追加,重启不会丢历史。

8. worktree 里改不动 git。 多半是两路同时操作同一分支,或者主仓库有未提交改动导致 worktree 状态混乱。规矩:一路一分支,主仓库保持干净。

9. 会话凭空消失。 tmux server 挂了或机器重启,会话全没。所以任务卡和日志必须落盘——这也是本方案把“记忆”放在文件而不是会话里的原因。恢复流程:up.sh 重新拉起,dispatch.sh 重派没完成的任务卡。

10. 并行路数一多就集体变慢。 先怀疑资源(CPU、磁盘、测试端口冲突),再怀疑额度限制。减路数、错峰、把重任务放到单独一路,通常就恢复了。

下一步建议

  • 加桌面通知:在任务卡里要求“完成后写 HANDOFF.md”,然后用一个 inotifywait(Linux)或 fswatch(macOS)小脚本监听 HANDOFF.md 出现,调系统通知。比盯着屏幕划算。
  • 让 peek 更有信息量:把 peek.sh 的输出接到一个静态 HTML 里定时刷新,浏览器标签页就是看板。
  • 把任务卡变成队列:tasks/ 里放 01-*.md、02-*.md,写个循环:某一路空了就取下一张。这已经是很轻量的自建调度器了。
  • 加一道自动体检:每路跑完自动 git diff --stat,超过预期文件数就报警——防止智能体“顺手重构”了你没让它碰的模块。
  • 固化自己的拆会话阈值:跑上一周,记录每次是哪一路先撞上下文上限、当时在做什么任务。你的阈值会比任何通用建议都准。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。