news 2026/9/16 20:55:30

Worktrunk `/wt-switch-create` 技能实战:一条命令创建 Git worktree 并将 Agent 会话迁入其中

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Worktrunk `/wt-switch-create` 技能实战:一条命令创建 Git worktree 并将 Agent 会话迁入其中

Worktrunk/wt-switch-create技能实战:一条命令创建 Git worktree 并将 Agent 会话迁入其中

【免费下载链接】worktrunkWorktrunk is a CLI for Git worktree management, designed for parallel AI agent workflows项目地址: https://gitcode.com/GitHub_Trending/wo/worktrunk

导读

Worktrunk(wtCLI)为 Git worktree 并行开发而生,而/wt-switch-create是其 Claude Code 插件内置的一条 Agent 技能(skill):当用户要求"在一个独立 worktree 里启动一个会话"时,Agent 通过该技能先创建 worktree,再把当前会话的工作目录重定向进去,随后在隔离环境中执行任务。本文从技能定义、参数语法、三步执行流程、底层EnterWorktreewtCLI 的协作机制、失败恢复与清理策略出发,结合仓库中的插件 hooks 与 Rust 源码,完整还原这条命令的设计与实现。读完你既能熟练使用/wt-switch-create,也能理解为什么"先创建、后进入"的流程被刻意设计成"错误驱动"而非"预先判断"。

技能概览:/wt-switch-create是什么

/wt-switch-create的官方定义位于 SKILL.md,其 frontmatter 声明如下:

  • name:wt-switch-create
  • description: 创建一个新的 worktrunk worktree(可选地在另一个仓库中),并将本次会话的工作目录切换进去;适用于"启动一个应在自己 worktree 中工作的会话"这一场景
  • argument-hint:[<branch>] [<repo>] [-- <task>]
  • compatibility: 需要wtCLI(Worktrunk)

插件 README 在 plugins/worktrunk/README.md 中给出了最直观的用例:

/wt-switch-create fix-auth -- Investigate the 5-minute session timeout

这条命令会:在 Worktrunk 默认的兄弟布局(<repo>.fix-auth/)下创建一个名为fix-auth的 worktree → 将会话切换进去 → 在该 worktree 内开始"调查 5 分钟会话超时"这一任务。分支名是可选的(/wt-switch-create -- <task>同样合法),创建出的 worktree 在会话结束后仍然存在,可以像其他 worktree 一样用wt merge/wt remove合并或删除。

值得注意的是,该技能虽随插件一并分发到 Codex 与 Gemini(见 CLAUDE.md 中"共享skills/暴露wt-switch-create"一节),但EnterWorktree依赖 Claude Code 的会话工作目录切换能力,另外两个工具无法真正执行它——这是项目明确接受的设计取舍。

参数语法与解析规则

技能的参数语法为:

[<branch>] [<repo>] [-- <task>]

三个参数的语义:

参数是否必选含义
branch可选新 worktree 的分支名;省略时由 Agent 在第 1 步挑选
repo可选目标仓库路径;指定后在该仓库中创建 worktree,而非当前会话所在仓库
task可选进入新 worktree 后要执行的任务;没有任务则进入 worktree 并等待

--之前的 token 是分支名和/或仓库路径,判定规则非常明确:

  • 路径形态的 token(以/~./../开头)→ 是 repo;
  • 其余任何 token→ 是 branch(例如docs永远被当作分支名,而不是docs/目录);
  • 如果--之前出现多个分支形态的 token,不符合语法 → Agent 应询问用户;
  • 没有--时,Agent 自行判断任务从哪里开始:前导 token 如果读起来像分支名(如fix-auth)或仓库路径则被消费掉,其余部分是任务;否则整个输入都是任务(fix the parser bug没有分支形态的前导,整体是任务)。

官方给出的四组示例:

/wt-switch-create my-feature -- fix the parser bug /wt-switch-create -- fix the parser bug /wt-switch-create my-feature ~/workspace/other-repo -- fix the parser bug /wt-switch-create my-feature

核心执行流程:先创建,再进入

技能规定:每一次调用,创建 worktree 都优先于任何其他工作。调用本身即显式创建请求——即使是研究类或只读类任务也会照常创建。这正是"Why creation is unconditional"一节(rationale.md)刻意设计的:历史教训是,当模型拿到研究/只读任务时,会自作主张认为"不需要隔离"而跳过创建。因此技能把调用本身定义为显式请求,而非"授权"(Scope只声明边界,不给权限暗示)。

第 1 步:挑选分支名

如果用户未指定分支名,Agent 需要自己取一个:要求、来自任务描述、与现有 worktree 命名保持一致;会话中途使用时,则从正在迁移的工作中提取;实在无可依据时,询问用户

第 2 步:无 repo 参数 → 一步完成创建与进入

当没有repo参数时,直接调用 Claude Code 的EnterWorktree({name: "<branch>"})。这一条调用隐含了完整链路:

  1. EnterWorktree触发 Worktrunk 插件的WorktreeCreatehook;
  2. hook 内部执行wt switch --create <branch> --no-cd --format=json
  3. 结果是位于默认布局中的一个普通wtworktree
  4. 由于EnterWorktree({name})不携带path参数,Claude Code 的"进入 worktree 确认弹窗"(rationale 中称 M2 confirmation)不会触发——这是该路线最重要的价值:任何配置都无法让path路线免去确认,唯独name路线天然跳过。

成功后执行任务;没有任务文本则确认就绪并等待。

会话中途迁移未提交工作:如果当前会话已有未提交改动,需要在EnterWorktree之前执行git stash push -u,进入新 worktree 后再git stash pop。git 的 stash 是按仓库共享的(rationale.md 已验证:在一个 worktreestash push -u的内容,可以在另一个 worktree 用git -C <path> stash pop干净地弹出,含未跟踪文件),所以跨 worktree 迁移完全可行。

第 3 步:其他情况 → 用wt创建,按路径进入

有两种情况会落到第 3 步:

  1. 带了repo参数——第 2 步的EnterWorktree({name})无法指定仓库;
  2. 第 2 步失败——错误信息会指明原因,典型两种:
    • ✗ Branch <branch> already exists(分支已存在)
    • Already in a worktree session(会话已在某个 worktree 中)

此时用Bash调用执行(当前仓库省略-C <repo>):

wt -C <repo> switch --create <branch> --no-cd --format=json

stdout 是 JSON,其中path字段是 worktree 的绝对路径(所有人类可读的状态行都走 stderr)。拿到 JSON 后调用EnterWorktree({path: "<path from the JSON>"})

针对Branch <branch> already exists的专门处理

  • 如果分支名是用户指定的→ 去掉--create重跑(wt switch <branch>会进入该分支,若 worktree 缺失则顺带创建它);
  • 如果分支名是第 1 步 Agent 自己挑的→ 另挑一个名字重试;
  • 其他任何失败(不是 git 仓库、分支名非法等)→ 报告错误并停止。

这个"先跑便宜的调用、读错误、再回退"的模式正是 rationale 反复强调的**错误驱动(error-driven)**设计:预测性守卫(predictive guards)和额外路由都试过并被删掉了,因为每个失败都自带逃生路线,第 3 步根本不需要预检查。

进入(EnterWorktree)后的三种结果处理

EnterWorktree({path})的结果分三种,技能对每种都有明确剧本:

1. Accepted(接受)

会话被正式 re-root 到 worktree 中。执行任务;无任务文本则确认就绪并等待。

2. Tool error(工具报错)

工具运行了但返回错误(如Cannot enter worktree: …),这是可优雅处理的情况:什么都没被移动,一个恢复方案覆盖所有此类错误。常见诱因:

  • 当前 cwd 解析不到任何 git 仓库(例如后台任务里,cwd 落在只装着仓库的~/workspace之类的非 git 父目录);
  • cwd 解析到与目标不同的仓库;
  • 会话已经扎根于某个 worktree(或是 pinned agent),此时只能进入当前仓库的.claude/worktrees/,连同仓库的wt兄弟 worktree 都进不去。

恢复测试就是一次cd:能否cd进 worktree,取决于它是否位于允许的目录内。所以执行cd <path>并观察结果:

  • 没有Shell cwd was reset提示cd生效了,worktree 可达。可以在里面工作,但注意:裸cd不是被追踪的 re-root,跨轮次(以及衍生子 agent 中)cwd 可能回退到会话启动时的 worktree。因此要用git -C <path>/wt -C <path>来固定命令,而不是依赖cd的持久性;
  • 出现Shell cwd was reset→ 不可达。停止并请用户让它可达:把仓库或父目录(如~/workspace)加入permissions.additionalDirectories(持久,覆盖每个会话),或运行/add-dir <path>(仅本次会话)。然后继续。不要在每个命令都被cd重置的情况下硬啃绝对路径。

3. Denied(被拒绝)

调用本身被拒绝且没有工具错误。无论拒绝措辞如何,这就是用户对"进入.claude/worktrees/之外 worktree 的确认弹窗"的答复——除非当时没有用户可问(拒绝信息表明会话无法弹出提示),这种情况什么都不决定,走上面的恢复路径。在用户的答复上:wt刚创建的 worktree仍然存在,只是没有进入。报告其路径并询问如何继续——因为通过cd到达它会推翻用户刚才的答复。

rationale.md 解释了为什么必须这样"结构性"地区分工具错误与用户拒绝,而不是解析拒绝措辞:拒绝文本无法可靠分类——用户输入的 "no" 会以通用措辞The user doesn't want to proceed with this tool use到达,既不点名工具也不点名确认;盲测中,任何让 Agent 去"识别拒绝措辞"的写法都会把 Agent 送进恢复流程、送进用户刚刚拒绝的 worktree。因此拒绝分支以停止开头,唯一的例外是"没有用户可问"。

清理策略:worktree 的两种生命周期

技能明确:创建的 worktree 是普通的 Worktrunk worktree——出现在wt list中,像其他 worktree 一样用wt merge/wt remove <branch>合并或删除。不要未经请求擅自删除

两种来源的 worktree 生命周期不同:

来源生命周期
第 2 步(EnterWorktree({name}))创建会话结束时若从未触碰(无改动文件、无提交),会被自动清理,分支一并删除;一旦写入了任何内容就保留
第 3 步(EnterWorktree({path}))创建总是保留

会话中途用户要求离开时,ExitWorktree({action: "keep"})将会话送回原始目录;ExitWorktree无法删除按path进入的 worktree,所以删除这类 worktree 一律用wt remove <branch>

rationale.md 进一步解释了第 2 步 worktree 的自动清理机制:未触碰的 worktree 在会话退出时通过插件的WorktreeRemovehook 移除,该 hook 执行的就是wt remove;干净且已完全合并的分支随之删除。已端到端验证:EnterWorktree({name: "probe"})/exitrepo.probeprobe分支都不复存在;只要有一个未跟踪文件,退出就会报告 "Keeping worktree…" 并保留两者。这是该规模下的特性——什么都没写的调研任务不会留下需要清理的东西,这也是第 2 步 worktree 不被描述为"持久"的原因。

作用域(Scope)边界

技能的命令授权范围是一个worktree(若指定了仓库,则是该仓库中的)以及其中的请求任务。提交(commit)、推送(push)、合并(merge)仍然各自需要用户明确许可——Scope只陈述边界,不授予权限,这是对"授权"措辞引发模型跳过创建的刻意修正。

底层原理:两套机制的组合(rationale 的 M1/M2)

rationale.md 用大量实测数据(Claude Code 2.1.173/2.1.177/2.1.220,wt v0.57.0-16 与 v0.69.2 实机验证)归纳出两套移动会话位置的机制,二者可组合

M1 ——cd(shell cwd)

移动 shell 工作目录;状态栏和工具路径相对化(Write(foo/bar.py))会跟随它。

  • 门槛:路径必须位于已配置的工作目录内——会话基础 cwd 加上permissions.additionalDirectories(settings.json)中的每个条目、启动时的--add-dir、或会话中的/add-dir
  • 目录内 →cd跨 Bash 调用持久;目录外 → harness 将其弹回并追加Shell cwd was reset to <original>
  • 对仓库盲目:只检查路径位置,不检查路径属于哪个 git 仓库。配置了/tmp后,/tmp下另一个仓库的 worktree 也可达;
  • 单个Bash 调用内,cd X && cmd永远有效;重置只发生在调用之间(子 agent 线程则每个调用之间都重置)。

M2 ——EnterWorktree({path})(正式 re-root)

正式重设会话的 worktree 归属(退出时被追踪)及其 cwd。

  • 门槛:必须是当前 cwd 解析到的仓库的 worktree,按会话状态分档:
    • 普通/首次进入的会话 → 该仓库注册的任何 worktree(git worktree list),磁盘上任意位置;多仓库工作区中还包括嵌套在该仓库内的仓库注册的 worktree;
    • 已在 worktree 会话中或 pinned agent → 仅限该仓库的.claude/worktrees/之下,连同仓库的兄弟 worktree 都拒绝
    • cwd 不在任何 git 仓库 → 完全拒绝;
  • 确认弹窗:安全检查基于调用携带的两个事实——path参数 + 目标位于项目.claude/worktrees/之外——在任何事发生前询问,对话框文案为 "permission-root relocation to<path>— a model-supplied worktree outside .claude/worktrees/"。只有是/否:没有"始终允许",同意后也不持久化,且permissions.allow中针对EnterWorktree的条目(裸名、(*)或路径 glob)无法抑制它。bypassPermissions免询问允许;无法弹提示的会话免询问拒绝。EnterWorktree({name})不传path,因此从不询问;
  • 仓库从 cwd 读取EnterWorktree本身永远不会把你移到另一个仓库——它只在你已站立的仓库内 re-root。要进入另一个仓库,先cd进去,再EnterWorktree(已实测:从 worktrunk 会话cd/tmp下的 prql worktree,然后EnterWorktree在 prql 内完成 re-root)。

两者如何组合:additionalDirectories是唯一总闸

目标cwd 可达?结果
同仓库(含其兄弟 worktree)总是EnterWorktree直接 re-root
配置目录下(~/workspace/tmp)的另一仓库cd进入即可工作;普通会话中EnterWorktree也可在其内 re-root
所有配置目录之外的另一仓库不可达——把它(或父目录)加入additionalDirectories,或/add-dir
任何仓库之外不适用无法 re-root

一旦仓库或其父目录(如~/workspace)进入additionalDirectories,会话就能cd进该仓库的 worktree,既能就地工作也能在其内 re-root。Agent自己无法扩大这个集合(/add-dir必须用户输入;唯一的自动添加是"符号链接解析到同一 cwd"这一窄场景),所以"cwd 和配置都够不到"的仓库是必须交还用户的真实边界——这也正是技能选择上报并给出具体修复(一次性配置~/workspace,持久覆盖所有未来跨仓库任务)而不是默默降级为绝对路径模式的原因。

为什么必须用--no-cd

--no-cd是技能命令中不可省去的关键旗标,rationale.md 从 Claude Code 的 Bash 工具实现给出了原因:Bash 工具不是裸 shell——它会从快照重放用户的 shell 启动配置,因此装了 wt shell 集成的用户,其wtwrapper 函数会进入工具内部执行。此时wt以集成模式运行,普通wt switch会给 wrapper 一个 cd 指令,把工具的 cwd 移走——这是与EnterWorktree竞争的第二个、未被追踪的 re-root--no-cd跳过该指令,让EnterWorktree成为唯一的 re-root。

已实测:没有--no-cd时,wt switch <branch>移动了会话,且新 cwd 持续到下一个 Bash 调用。对于从未安装集成的机器(新 shell、CI),wrapper 不存在,wt无论如何都无法 cd,所以--no-cd在集成机器上是关键、在其他场合是空操作——但不要去掉它

对应到wtCLI 本身,src/cli/mod.rs 中--no-cd的 clap 定义为"切换后跳过目录变更"(hooks 照常运行,适合 tmux 工作流或 CI/自动化;--execute也会在调用目录启动;可用--cd覆盖)。同文件 src/cli/mod.rs 对--format的说明更直接:JSON 输出结构化结果到 stdout,专为工具集成设计(例如 Claude Code 的 WorktreeCreate hooks)

源码佐证:hook 管线与wt switch的落地实现

WorktreeCreate hook:wt switch --create的调用现场

插件 hooks 定义在 hooks.json,其中WorktreeCreate事件执行:

bash -c 'set -o pipefail; name=$(jq -er .name) || exit 1; cd "${CLAUDE_PROJECT_DIR:-.}" || exit 1; bash "$CLAUDE_PLUGIN_ROOT/hooks/wt.sh" switch --create "$name" --no-cd --format=json | jq -er .path'

这条管线值得拆解:

  1. set -o pipefail是刻意包在bash -c里的:hook 命令由 harness 以shell: true(即/bin/sh -c)派生,Linux 上多是 dash,而 dash 从 0.5.12 起仍不支持pipefailset是 POSIX 特殊内建,dash 会致命报错);实测还有用户的 hooks 跑在 fish 下(worktrunk PR #2962),fish 根本没有 shell options。显式bash -c保证wt失败时不会被尾部的jq吞掉(没有 pipefail 时,空输入会让末尾jq以 0 退出,Claude Code 会看到一个"成功"但无 path 的 hook);
  2. name=$(jq -er .name)从 harness 传入的 JSON 提取分支名;
  3. wt.sh switch --create "$name" --no-cd --format=jsonwtCLI 创建;
  4. jq -er .path把 worktree 绝对路径作为 hook 输出——hook 契约要求 stdout 的最后一个非空行是已存在的目录,而技能只读取工具结果,契约是 hook 自己的事。

wt switch --create在 Rust 侧的行为

wt switch的实现位于 src/commands/worktree/switch.rs。创建分支时的约束校验在resolve_switch_target中:当--create且分支已在本地存在时,直接返回GitError::BranchAlreadyExists——这就是技能第 3 步捕获的✗ Branch <branch> already exists的来源,rationale.md 实测该错误退出码为 1,且无论分支有无 worktree 都会触发。wt switch <branch>(不带--create)对已存在分支返回 0:worktree 缺失则创建(JSON 中"action":"created","created_branch":false),已存在则重新进入("action":"existing")。这也解释了第 3 步的"重跑去掉--create"回退为何成立:不带--create时分支必须已存在,正好命中已存在分支的场景。

技能中 "JSON 只走 stdout、人类可读状态行走 stderr" 的说法同样有据可依:--format=json被设计为"输出结构化结果到 stdout,供工具集成(如 Claude Code WorktreeCreate hooks)使用"(src/cli/mod.rs),这使得从 stdout 提取.path字段是安全的——hook 输出和状态行不会混入。

wt switch参考文档的对应

技能所依赖的wt switch语义在 switch.md 有完整描述:--create--base(默认分支)创建新分支;不带--create时分支必须已存在;创建流程为 pre-switch hooks(阻塞)→ 在配置路径创建 worktree → 切换目录 → pre-start hooks(阻塞)→ 后台 spawn post-start 与 post-switch hooks。技能中的--no-cd --no-hooks组合(worktrunk skill 的"并行子 Agent"配方,见 worktrunk/SKILL.md)正是围绕这套 hook 时序的自动化用法。

wt remove的清理语义

技能清理部分依赖wt remove的分层行为(rationale.md 实测):脏 worktree → 拒绝(退出码 1,提示--force);干净但未合并提交 → 删除 worktree、保留分支,提示wt remove -D;干净且已合并 → worktree 与分支一并删除。WorktreeRemovehook(hooks.json)执行wt.sh -C <path> remove --foreground <path>,其中--foreground是关键:后台删除会在原路径留下占位目录(为保持 shell PWD 有效),会阻塞后续wt switch("Directory already exists"),这一细节在 switch.rs 中也有注释佐证。

已知限制(设计内取舍)

rationale.md 明确列出技能刻意接受的边界:

  • 跨仓库只经additionalDirectories可达:之外则上报一次性的配置修复,而非降级为绝对路径模式;
  • pinned 或已在 worktree 中的会话连同仓库兄弟 worktree 都无法重进(更严格的.claude/worktrees/检查),落入同样的可达性测试与上报;
  • 落入第 3 步的调用仍会各询问一次确认(M2):另一个仓库、已存在分支、同一会话中的第二个 worktree。技能范围内无法消除——检查忽略permissions.allow,而把项目的worktree-path指进.claude/worktrees/虽能满足检查,却等于放弃 Worktrunk 默认布局、把wt和 Claude Code 放进同一目录,作者未实测过这种组合;
  • wt switch --create不是幂等的:若上游将来改为"存在即进入",第 3 步的已存在分支重试将塌缩消失,hook 也不再在已存在分支上失败——那会移除第 3 步存在的两个理由之一。

结语

/wt-switch-create表面是一条参数极简的 Agent 命令,内里却是一套经过实测验证、刻意"错误驱动"的设计:EnterWorktree({name})借由WorktreeCreatehook 复用wt的完整能力并跳过确认;wt -C <repo> switch --create --no-cd --format=json解决仓库定向、已存在分支与机器可读输出;--no-cd保证 shell 集成不会与EnterWorktree争抢 re-root;additionalDirectories成为跨仓库可达性的唯一总闸。理解这套机制,你不仅能顺畅使用该技能,还能在自己的并行 Agent 工作流中复用它——例如wt switch --create <branch> --no-cd --no-hooks预创建 worktree、再以显式路径提示子 Agent(worktrunk/SKILL.md 的并行子 Agent 配方)。技能的全部设计依据与实测记录,可继续阅读同目录的 rationale.md。

【免费下载链接】worktrunkWorktrunk is a CLI for Git worktree management, designed for parallel AI agent workflows项目地址: https://gitcode.com/GitHub_Trending/wo/worktrunk

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 20:55:07

报 401 了?Trae IDE 访问 K3,Base URL 抄 TaoToken 的 /api 再试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 20:54:41

Redis String编码探秘:44字节边界与int/embstr/raw实测对比

先说结论&#xff1a;44 字节这个数字真有来源&#xff0c;但它不是性能悬崖&#xff0c;更不是让你背下来的面试题。我在自己的测试机上&#xff0c;把 Redis String 的三种编码——int、embstr、raw&#xff0c;从 43 字节到 45 字节的边界处一路压到 100 字节&#xff0c;一…

作者头像 李华
网站建设 2026/9/16 20:54:10

去水印批量下载:douyin-downloader 从配好到跑通的实操笔记

去水印批量下载&#xff1a;douyin-downloader 从配好到跑通的实操笔记 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback …

作者头像 李华
网站建设 2026/9/16 20:52:04

SQL报错注入实战详解:CTFHUB场景下UpdateXML、ExtractValue与Floor三种手法

CTF圈的兄弟应该都对SQL注入不陌生&#xff0c;在CTFHUB技能树里&#xff0c;报错注入算是基础里很有代表性的一类题型。它不像联合查询那样需要看着回显位置拼字段数&#xff0c;也不像盲注那样一个个字符去猜&#xff0c;而是在页面报错信息里直接“看到”数据库吐出来的数据…

作者头像 李华