Windmill 多 Worktree 开发环境实战:基于 workmux、tmux 与 Claude Code 的并行开发工作流
【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmill
本篇技术指南围绕 Windmill 仓库根目录的 README_WORKMUX_DEV.md 展开,系统讲解基于workmux的多 worktree 开发环境:每个 git worktree 拥有独立的 tmux 窗口,内含 Claude Code 智能体、自动热重载的后端服务和前端 dev server,并通过"端口槽(Slot)"机制在隔离端口上并行运行。读完本文,你将掌握从安装依赖、创建/打开/合并/清理 worktree,到 SSH 远程端口转发、企业版(EE)代码挂载、Cargo features 传递等完整实战能力,并能结合 scripts/worktree-env、scripts/worktree-common.sh 等仓库脚本理解其底层实现原理。
环境概览:一个 Worktree 就是一个独立开发沙箱
Windmill 的 workmux 方案将"并行开发多个功能"彻底解耦:每创建一个 feature worktree,workmux 就会为其分配一个 tmux 窗口,窗口内固定布局三个面板(pane):
- Claude Code agent:负责接收你的自然语言任务(如"修复 auth.rs 的登录 bug"),直接在该 worktree 内读写代码、运行命令;
- 后端服务:以
cargo watch -x run方式运行,文件保存即自动重编译、重启,监听该 worktree 专属端口; - 前端服务:以
npm run dev方式启动,并将/api等请求代理到同 worktree 的后端端口。
三者共享同一份 worktree 工作目录,天然保证"改代码 → 后端重载 → 前端联调 → agent 交付"在同一沙箱内闭环。仓库根目录的 README_WORKMUX_DEV.md 是这套方案的权威说明,下文所有命令与配置均以其为准,并辅以仓库脚本佐证。
前置要求
在开始之前,需要准备以下工具链:
| 依赖 | 用途 |
|---|---|
| tmux | 承载 worktree 的窗口/面板布局 |
| Rust 工具链(rustup) | 编译并安装 workmux、cargo-watch,以及编译 Windmill 后端 |
| Node.js + npm | 运行前端 dev server 与 CLI 工具 |
| 本地 PostgreSQL | 后端数据库,连接配置见 backend/.env |
PostgreSQL 的配置细节可在 backend/.env 中查看(默认连接串形如postgres://postgres:changeme@127.0.0.1:5432/windmill,与 scripts/worktree-common.sh 中的约定一致)。
安装步骤
1. 安装 workmux
workmux 是这套工作流的编排核心,通过 Cargo 安装:
cargo install workmux2. 安装 Claude Code 插件
workmux claude install该命令让 workmux 具备在 worktree 面板中管理 Claude Code agent 的能力(创建、发送提示词、查看状态等)。
3. 安装 cargo-watch
用于在后端源码变更时自动重编译并重启服务:
cargo install cargo-watch4. 安装 llm CLI(自动分支命名必需)
workmux 使用llmCLI 根据任务提示词自动生成分支名。通过 uv 安装:
uv tool install llm llm install llm-anthropic随后配置 Anthropic API Key:
llm keys set anthropic # 按提示粘贴你的 API Key5. 推荐:Shell 别名与自动补全
为日常操作设置便捷别名,在~/.zshrc中添加:
alias wm="workmux"同时建议为 workmux 配置 zsh 自动补全,具体步骤参见 workmux 官方文档(workmux安装后可在其帮助信息中找到 autocomplete 子命令)。
端口槽(Slot)系统:隔离与可预测的端口分配
这是整套方案的基石。每个 worktree 会被分配一个slot,由 slot 唯一确定前后端端口:
| Slot | 后端端口 | 前端端口 |
|---|---|---|
| 0 | 8000 | 3000 |
| 1 | 8010 | 3010 |
| 2 | 8020 | 3020 |
| 3 | 8030 | 3030 |
| ... | ... | ... |
端口计算公式即8000 + slot*10与3000 + slot*10,这一点可直接在仓库脚本 scripts/worktree-env 中验证:
backend_port=$((8000 + WM_SLOT * 10)) frontend_port=$((3000 + WM_SLOT * 10))三条关键规则:
- Slot 0 保留给主 worktree(默认的
cargo run/npm run dev),端口为 8000/3000; - 不设置
WM_SLOT时,scripts/worktree-env 会自动扫描所有现存 worktree 的.env.local,找出被占用的 slot,再从 1 开始分配最小的空闲 slot,并在终端打印结果。脚本注释说明了为何采用"扫描已占用"而非"位置索引":worktree 被删除后位置索引会复用仍存活的 slot,导致端口冲突; - 设置
WM_SLOT=N时,强制使用该 slot;若端口已被占用,脚本会输出WARNING: Slot N ports (...) already in use警告(见 scripts/worktree-env 的port_in_use检查逻辑)。
分配结果会写入该 worktree 根目录的.env.local:
BACKEND_PORT=$backend_port FRONTEND_PORT=$frontend_port REMOTE=http://localhost:$backend_port其中REMOTE被前端 vite 配置读取,作为/api请求的代理目标——这与 frontend/vite.config.js 中process.env.REMOTE ?? (process.env.BACKEND_PORT ? \http://localhost:${process.env.BACKEND_PORT}` : 'https://app.windmill.dev/')` 的取址逻辑一一对应。
SSH 端口转发:远程开发本地访问
如果你通过 SSH 远程开发,可在本地机器的~/.ssh/config中为每个 slot 预配置隧道:
Host windmill-dev HostName <remote-ip> User <username> # Slot 0 (main worktree) LocalForward 8000 localhost:8000 LocalForward 3000 localhost:3000 # Slot 1 LocalForward 8010 localhost:8010 LocalForward 3010 localhost:3010 # Slot 2 LocalForward 8020 localhost:8020 LocalForward 3020 localhost:3020 # Slot 3 LocalForward 8030 localhost:8030 LocalForward 3030 localhost:3030之后只需连接一次,所有隧道即全部生效:
ssh windmill-dev随后在本地浏览器访问http://localhost:<frontend-port>即可看到对应 worktree 的前端界面。端口槽的"可预测性"正是为了配合这种静态隧道配置:slot 一旦确定,端口就永远可预期,无需每次手动改配置。
快速上手:创建、打开、协作与清理
创建 worktree
# 创建新 worktree(自动分配 slot,打印端口) workmux add my-feature # 或显式指定 slot WM_SLOT=2 workmux add my-feature # 创建 worktree 并立即向 agent 发送提示词 workmux add -A -p "fix the login bug in auth.rs"add命令只创建 worktree 而不会自动打开它。创建后查看分配的端口:
cat <worktree-path>/.env.local-A参数则会在创建后立即打开 worktree,并把-p指定的提示词直接交给 Claude Code agent 开始工作。
打开 worktree
workmux open my-feature该命令打开一个 tmux 窗口,包含三个面板(agent 面板获得焦点):
- Claude Code agent(聚焦面板)
- 后端:
cargo watch -x run,监听该 worktree 分配的端口,保存即自动重载 - 前端:
npm run dev,将请求代理到同一 worktree 的后端
向 agent 派发任务
# 向 worktree 中的 agent 发送提示词 workmux send my-feature "fix the login bug in auth.rs" # 查看所有 agent 状态 workmux status合并与清理
Windmill 团队的协作纪律是:绝不直接 merge worktree,一律通过 GitHub PR 合入 main(可以请 worktree 内的 Claude Code agent 代为创建 PR)。PR 合并后:
# 关闭 tmux 窗口但保留 worktree workmux close my-feature # PR 合并后,删除 worktree、分支与 tmux 窗口 workmux rm my-feature注意:不要使用
workmux merge。所有变更都必须经过 PR 流程进入 main。
rm的清理动作由 scripts/pre-remove.sh 与 scripts/worktree-cleanup 承载:二者都会先调用wm_kill_processes_from_env_file依据.env.local中的端口杀死对应进程,再执行wm_shared_pre_remove删除该 worktree 专属数据库并移除对应的 EE worktree(见 scripts/worktree-common.sh)。
配置解析:post_create 钩子与依赖预置
文档描述的.workmux.yaml位于仓库根目录(由 workmux 管理,每个开发者本地生成),关键配置段如下:
post_create:worktree 创建后执行 scripts/worktree-env,生成带端口分配的.env.local;panes:定义 tmux 布局(agent / 后端 / 前端三面板);files.copy:将 backend/.env 和scripts/目录复制进每个 worktree。
post_create钩子还会用cp -a复制frontend/node_modules——这是刻意为之:cp -a保留符号链接,而cp -r会解引用.bin/下的符号链接,破坏可执行入口。
这套"复制依赖"的完整逻辑在 scripts/worktree-common.sh 的wm_copy_dependencies中实现,除frontend/node_modules外还包括:
- 复制主仓库的
backend/.env; - 复制
cli/node_modules,并在 worktree 内执行npm install && npm run gen-client重新生成 CLI 客户端; - 复制编译好的
wm-ts-nav二进制(wm-ts-nav/target/release/wm-ts-nav)。
每个 Worktree 独立数据库
wm_shared_post_create中的wm_setup_database会为每个 worktree 创建独立数据库,命名规则为windmill_${worktree名中的-替换为_},并自动执行迁移:
- 默认
CREATE DATABASE空库后,用sqlx migrate run --source backend/migrations应用全部迁移(迁移文件位于 backend/migrations); - 若设置
WM_CLONE_DB=1,则以windmill库为模板克隆(CREATE DATABASE ... TEMPLATE windmill),适合需要完整演示数据的场景; - 还会从主库的
global_settings中复制license_key,保证 EE 功能可用; - 生成的
DATABASE_URL会被追加写入.env.local。
对应地,删除 worktree 时数据库会被DROP DATABASE ... WITH (FORCE)清理,避免垃圾库堆积。
按需启动的前端 Dev Server(资源优化)
并行打开多个 worktree 时,前端 dev server 的内存开销不可忽视:一旦页面被访问,单个 vite dev server 常驻内存达1.1–1.7 GB。为此仓库提供了 frontend/scripts/dev-supervisor.mjs,由"监督进程"独占公开端口,仅在收到首个连接时才拉起真实 dev server(冷启动到可服务约 1 秒),流量停止后按空闲超时回收:
node scripts/dev-supervisor.mjs # 本 worktree,使用 $FRONTEND_PORT node scripts/dev-supervisor.mjs -t 3340:/path/wt-a -t 3350:/path/wt-b --idle 15m node scripts/dev-supervisor.mjs -t 3340:/path --idle 10m --stats rss.jsonl node scripts/dev-supervisor.mjs -t 3340:/path --bind 0.0.0.0 # 允许跨主机访问关键参数说明:
-t/--target <port>[:<cwd>]:指定监督端口与对应 worktree 目录,可重复传入管理多个 worktree;省略时默认$FRONTEND_PORT与当前目录;--idle <时长>:空闲回收阈值,支持ms/s/m/h后缀,默认15m;代码注释特别提醒该值必须大于应用自身的 5 分钟后台轮询休眠期,否则"从未休眠的标签页"可能被误回收且无法通过 websocket 重新唤醒;--stats <文件>:周期性记录各目标 RSS 样本(JSONL),便于观测内存占用;--bind:默认仅绑定127.0.0.1,如需跨主机访问才显式放宽。
其代理位于 TCP 层,HTTP、HMR websocket 与/api代理均原样穿透;WebSocket 连接不会唤醒已回收的服务(前端 y-websocket 会无条件重连,若允许唤醒会导致常驻),休眠标签页的唤醒依赖应用侧的 dormancy 预热机制。此外,前端应用在标签页非活跃 5 分钟后会挂起后台轮询(VITE_DEV_DORMANT_MS),进一步降低常驻内存;但该机制仅对明文 HTTP 生效——HTTPS=true时 HMR 连接与真实流量无法区分,打开的标签页会保持服务存活。这一优化策略的完整说明见 frontend/README_DEV.md。
企业版(EE)代码访问
Windmill 的企业版源码位于独立私有仓库windmill-ee-private(本仓库的兄弟目录)。创建 worktree 时,scripts/worktree-common.sh 的wm_setup_ee_worktree会自动在 EE 仓库中创建同名分支的配套 worktree,并写入.claude/settings.local.json,通过additionalDirectories授权 Claude Code 访问 EE 代码。
EE worktree 分支的确定顺序(源码可见):
- EE 仓库存在同名本地分支 → 直接
worktree add; - 否则存在
origin/<branch>→--track创建并跟踪; - 否则以 backend/ee-repo-ref.txt 中固定的 commit 引用(CI 读取同一文件)创建新分支——这是刻意设计:EE 仓库本地的
main不会快进更新、会偏离 pin,而基于 pin 创建的 worktree 与 CI 编译的代码一致,保证本地cargo check --features private结果可信; - 兜底:从 EE 仓库
main创建新分支。
此外还会执行 backend/substitute_ee_code.sh(-d指定 EE worktree 目录)完成本地代码替换,使 CE 工作区与 EE 源码正确衔接。
Sandbox 模式的挂载配置
如果 workmux 以 sandbox 模式运行,容器需要显式挂载才能访问 EE 仓库。在全局配置~/.config/workmux/config.yaml中添加:
sandbox: extra_mounts: - host_path: ~/windmill-ee-private writable: true - host_path: ~/windmill-ee-private__worktrees writable: true第一个路径挂载 EE 主仓库(主 worktree 使用),第二个挂载 EE worktrees 目录(feature worktree 使用),确保每个 sandbox 容器都能读到对应分支的 EE 源码。
Cargo Features 传递
构建后端时如需启用特定 Cargo features(如enterprise、parquet),通过环境变量CARGO_FEATURES传递。后端面板会从.env.local读取该值,并追加--features <value>到cargo watch命令。
配合wm别名使用:
CARGO_FEATURES="enterprise,parquet" wm add my-feature创建 worktree 时,post_create钩子(scripts/worktree-env)会把CARGO_FEATURES写入.env.local:
if [[ -n "${CARGO_FEATURES:-}" ]]; then echo "CARGO_FEATURES=$CARGO_FEATURES" >> .env.local fi后端面板启动时自动读取并生效,无需手工干预。
登录与本地验证
worktree 环境启动后,访问对应前端端口即可登录:
- 默认账号:
admin@windmill.dev - 默认密码:
changeme
配合"每 worktree 独立数据库 + 自动迁移"的机制,登录后即可在完全隔离的环境中验证当前分支的前后端行为。
小结
Windmill 的 workmux 开发流程本质上是把"并行开发"工程化:git worktree 隔离代码,slot 端口系统隔离运行环境,独立数据库隔离数据,Claude Code 面板承接编码任务。其配套脚本(scripts/worktree-env、scripts/worktree-common.sh、frontend/scripts/dev-supervisor.mjs)沉淀了大量工程细节——符号链接保留、slot 冲突规避、EE 分支与 pin 对齐、dev server 按需启停——为大规模多分支并行开发提供了可直接复用的参考实现。新手可按"安装 →wm add→wm open→wm send"四步上手,进阶团队则可进一步定制post_create钩子与 sandbox 挂载,把该模式推广到任意复杂度的仓库。
【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmill
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考