Skip to content

mihaibojin/worktrees

v0.9.0Apache-2.0

Start branches, read CI, rebase onto the head branch, describe, ship and merge pull requests, and prune finished worktrees through the worktrees CLI

worktrees, as an agent plugin

Seven skills for Claude Code and Codex over the worktrees CLI. Each is /worktrees:NAME in Claude Code and $worktrees:NAME in Codex, and an agent reaches for one on its own when a task matches its description, except preflight, which runs only when you call it.

skilldoes
new-branchfetches, then starts a branch off the remote's head
fast-forwardbrings this branch level with the head branch, through each rebase conflict
ci-checksreads a pull request's checks as JSON, and waits for them
describe-prchecks a pull request's title and body against its diff, and fixes them
ship-itcommits, pushes, opens the pull request, fixes CI and merges, unassisted, for this checkout or the branches it is given; or ships the open pull requests it is given
pruneremoves the worktrees and branches the CLI proves finished, after asking
preflightadds the permission rules ship-it needs to your Claude Code settings, after asking

Install

The plugin is listed in the mihaibojin marketplace, pinned to a release of this repository.

Claude Code:

claude plugin marketplace add MihaiBojin/agent-plugins
claude plugin install worktrees@mihaibojin

Codex:

codex plugin marketplace add MihaiBojin/agent-plugins
codex plugin add worktrees@mihaibojin

Requirements

Install the CLI from the same release:

uv tool install git-worktrees

The skills need jq, and gh or glab signed in for the forge the remote is on. The plugin installs none of them. Skills call gw from $PATH.

What runs without asking

Each skill pre-approves the reads it makes, gw sync and gw branch create. Three go further, because the extra step is the skill's job: describe-pr edits a pull request's title and body, fast-forward runs gw fast-forward and its conflict loop, and ship-it commits, pushes, opens and edits the pull request, reruns CI and merges. No skill pre-approves a removal, and outside ship-it nothing pre-approves a merge, a push or a CI rerun: those reach the client's own permission prompt.

A skill's own pre-approval does not reach far enough for ship-it. In Claude Code it lasts only the turn that invoked the skill, and auto mode blocks merging a pull request no human approved; in Codex a skill has none, and the sandbox has no network and a read-only .git. Neither client lets a plugin grant more, so the rules ship-it needs go in your own Claude Code settings or Codex rules, once. ship-it checks for them before it starts and names any that are missing. /worktrees:preflight user adds the Claude Code ones to ~/.claude/settings.json, and /worktrees:preflight project to the repository's .claude/settings.json, which reaches every machine that clones it once committed.

CI

gw ci-checks --json prints one snapshot: the review's identity, every check with its link, and one checksState. gw ci-checks --wait returns once no check is running, so an agent waiting on CI reads one result rather than one per poll. For a failing check the skill reads only the failing part of that job's log.

Pull request descriptions

The title and body become the squash commit, so one format serves both: an imperative title in the history's convention, then one line per change a reader can see in the diff. Every line has to be supported by the diff, the commits or a linked issue. The skill changes only lines that are wrong or missing, keeps release-note blocks, issue keywords and harness footers as they were, and says in its reply what it changed.

Ship it

/worktrees:ship-it takes the change in the checkout from uncommitted work to a merged pull request, and asking for it approves every step for that change: a branch off the head branch when you stand on it, commits by path, gw fast-forward --autostash, a leased or plain push, the pull request and its description, a release note through release-notes:write when the release-notes plugin is installed, the CI fixes, and the merge.

/worktrees:ship-it fix-parser does the same for the branch fix-parser, in the worktree that has it checked out, or a new one when none does. On a head branch that holds commits its remote does not, it starts the branch on them, ships it, then puts the head branch back where its remote is and prints the command that undoes that.

/worktrees:ship-it 41 42 ships open pull requests instead, one at a time and a stack bottom first. Asking approves pushing fixes to each one's branch, correcting its description and merging it once green, in the worktree that holds the branch, never the main checkout. A pull request stacked on one that did not merge in the same run is not merged.

It merges with .releasetools.yaml's release.merge or the first strategy the repository allows of squash, merge and rebase; a squash keeps GitHub's (#NN) subject and the branch's commit trailers, and --match-head-commit refuses a head that moved after CI passed. It never uses --admin, --auto or --delete-branch, and reports a merge queue's entry as queued, never as merged. It stops and reports where a person has to decide, such as a conflict whose sides want incompatible things, a push the lease rejects or a blocked merge. When it fixed CI, the pull request carries an auto-resolution block, one row per problem, which the squash message leaves out. The report ends with one row per problem: the pull request, what was wrong, what fixed it. --hold-merge does everything but the merge, and prints the one gh or glab command that makes it with the prepared subject and body.

Cleanup

The prune skill reads gw prune --json and gw branch status --json. Both sync first once the last sync is a minute old, unless WORKTREES_OFFLINE is set. If remote state is still never synced or older than 10 minutes, it asks whether to run gw sync; it runs sync only on your answer. A previous approval or refusal remains in force for that cleanup.

It lists eligible worktrees and loose branches, with proofs, and shows empty and stash-blocked rows separately. Ignored-file blockers remain visible. Those categories need explicit approval. --yes grants no deletion permission, and the plugin never targets its current checkout.

After you choose, each worktree goes through command gw remove with its exact quoted JSON path and only its approved --delete values. Selected loose branches follow through command gw branch prune. Both commands reassess first and can refuse a target that changed. The skill retains target exclusions and category choices across a refresh.

The report distinguishes checkout removal, branch deletion and stash outcomes, including partial failures. It keeps the CLI's exact restore commands. Branch restore recreates a ref, not a worktree directory. Stash restore remains possible until Git garbage collection prunes its unreachable commit. Ignored files cannot be restored.

The plugin uses CLI proof and protections. It does not delete remote branches or bypass a refusal with raw Git deletion or filesystem removal.

Fast-forward

The fast-forward skill runs gw fast-forward --json, which is what the shell plugin's gwff runs; an agent's shell has no plugin functions, so it calls the binary. Arguments to /worktrees:fast-forward pass through, so /worktrees:fast-forward next-parser --from release-1.2 names the branch a finished one moves to and where it starts. The ship-it skill runs it in the checkout holding the head branch once its pull requests land.

The CLI decides what happens: fast-forward, rebase, or a new branch when this one is already merged. When the rebase stops, the skill reads each conflicted file and what each side changed, edits the file to carry both, git adds it, runs GIT_EDITOR=true git rebase --continue and runs the command again, until the branch is level. It asks you when the two sides cannot both be kept, and before touching a rebase that was already stopped when it started, such as one paused in git rebase -i. It stages only the conflicted files. It reports each commit the rebase stopped at and how each file was resolved.

Those commands, git status, git diff, git log and git show run without a permission prompt. git rebase --abort runs only when you ask, and the skill never runs git rebase --skip, which drops a commit. A refusal from the CLI, such as uncommitted changes, is reported and not worked around.

Versions

The plugin carries the version of the release it ships in. scripts/bump_version.py writes it into .claude-plugin/plugin.json, .codex-plugin/plugin.json and plugin.json along with every other copy, so a client sees an update whenever the marketplace moves to a newer tag. plugin.json is the Agent Plugins manifest and holds only the fields that format names.