| name | branch |
| description | Create an isolated git worktree for a task and log it to a shared work journal, so multiple Claude Code sessions run in parallel without racing on HEAD, uncommitted edits, or the index. Subcommands: `list`, `done <slug>`, `update` (heartbeat), `coord <message>` / `coord clear <slug>` (cross-session constraints). Triggers on `/branch`, "start in a worktree", "isolate this work", "parallel session", "heartbeat the journal".
|
| model | sonnet |
/branch โ isolated worktree for parallel agent sessions
Two sessions in one working dir race on HEAD (git checkout switches it for everyone),
uncommitted edits (leak across checkouts), and the index. Worktrees give each session its own
working files, HEAD, and index while sharing .git. This wraps git worktree add with project
conventions + a journal entry. Cost: one directory, ~zero git overhead.
Argument shapes
/branch <task-slug> [--from <base-branch>] [--description "<text>"] [--reuse]
/branch list
/branch done <task-slug> [--force]
/branch update [--working-on "..."] [--state <state>] [--next "..."] [--blocked-by "..."] [--asks "..."] [--dont-touch "..."] [--slug <slug>]
/branch coord <message>
/branch coord clear <slug>
Parse the first positional arg and dispatch: listโlist mode, doneโdone mode, updateโupdate mode (heartbeat current entry), coordโcoord mode (manage ## Coordination), anything elseโcreate mode (treat the arg as a task slug).
Mode: create (default)
1. Resolve repo info
REPO_ROOT=$(git rev-parse --show-toplevel)
REPO_NAME=$(basename "$REPO_ROOT")
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
COORD_DIR=$(bash ~/.claude/coordination/resolve-coord-dir.sh)
JOURNAL="$COORD_DIR/journal.md"
If git rev-parse fails, tell the user they're not in a git repo and stop.
Journal path (applies in every mode): $JOURNAL = ~/.claude/coordination/<repo>/journal.md โ out of the tracked tree, shared by every worktree of the repo, never committed. Any reference below to docs/worktree-journal.md means $JOURNAL. Never edit the in-tree docs/worktree-journal.md โ it's a tombstone pointer, not the real journal.
2. Validate the slug, pick the base branch, resolve the description
- Validate the slug: must match
^[a-z][a-z0-9-]*$ (reject otherwise with a one-line example of a valid slug). Refuse if git rev-parse --verify "refs/heads/<slug>" finds an existing branch, unless --reuse was passed. Refuse if git worktree list --porcelain | grep -q "branch refs/heads/<slug>" finds a worktree already on that branch.
- Pick the base branch: use
--from <base> if given (verify with git rev-parse --verify <base>), otherwise CURRENT_BRANCH. If the working tree has uncommitted modifications (git status --porcelain | grep -v '^??' non-empty), warn that they won't carry over โ git worktree add checks out the base's HEAD, not the working tree โ and ask whether to proceed or commit/stash first.
- Resolve the description: use
--description "..." if given, otherwise ask: "One-line description of what this worktree is for? (will go in the journal)".
3. Pick the worktree path
Default: sibling of the repo dir.
WORKTREE_PATH="$(dirname "$REPO_ROOT")/${REPO_NAME}-${SLUG}"
Override: if ${REPO_ROOT}/.claude/worktree-base exists, read it as a path template; substitute {repo}โ$REPO_NAME, {slug}โ$SLUG (e.g. ../worktrees/{repo}/{slug}). Absolute-path templates are fine โ useful for a different filesystem.
Refuse if the resolved path already exists.
4. Create the worktree, run the setup hook
git worktree add "$WORKTREE_PATH" -b "$SLUG" "$BASE_BRANCH"
On failure, surface the error and stop โ don't write the journal entry.
A fresh worktree shares .git but not gitignored files (.dev.vars, .env, node_modules), so a new session can't build/test without them. If ${REPO_ROOT}/.claude/worktree-setup.sh exists, run it in the new worktree with REPO_ROOT/WORKTREE_PATH/SLUG/BASE_BRANCH in the env and the worktree as CWD:
if [ -f "$REPO_ROOT/.claude/worktree-setup.sh" ]; then
( cd "$WORKTREE_PATH" && REPO_ROOT="$REPO_ROOT" WORKTREE_PATH="$WORKTREE_PATH" \
SLUG="$SLUG" BASE_BRANCH="$BASE_BRANCH" bash "$REPO_ROOT/.claude/worktree-setup.sh" )
fi
That repo-authored script typically symlinks gitignored secrets from the main clone and installs deps (e.g. ln -s "$REPO_ROOT/.dev.vars" . then npm install). Surface its output. If it fails, keep the worktree (already created) and write the journal entry anyway โ report the failure for a manual re-run. If no hook exists, don't invent one: note it in step 6 and offer to scaffold one (symlink .dev.vars/.env + npm install), skipping when the repo has no package.json or env files.
5. Update the journal
If $JOURNAL is missing, create it with this header:
# Worktree Journal
Active and recent isolated worktrees for this repo. Agents starting new
sessions should review this. Mark entries `done` with `/branch done <slug>`
when the work ships or is abandoned.
## Active
## Done
Insert the new entry under ## Active (newest first):
### <slug>
- **Started:** <YYYY-MM-DD HH:MM> (use `date '+%Y-%m-%d %H:%M'`)
- **Branch:** `<slug>`
- **Worktree:** `<absolute path>`
- **Base:** `<base-branch>`
- **Description:** <description โ long-term goal, set at create, doesn't change>
- **Working on:** <one-line current focus, set to "(starting)" at create>
- **State:** building
- **Next action:** (starting)
- **Last update:** <same as Started>
- **Status:** active
Field semantics are defined once, in the Journal schema section at the end of this file. Omit Blocked by/Don't touch/Asks at create time โ /branch update adds them when populated.
If the journal is missing ## Coordination (legacy journal), insert it between ## Active and ## Done:
## Coordination
Cross-cutting constraints active across sessions. Format:
`<YYYY-MM-DD HH:MM> โ <slug>: <constraint>. <expires when or unblock condition>.`
- (none active)
Save the file โ it lives out of the tracked tree, so there's nothing to commit; the entry is live for all sessions the moment it's written.
6. Output to user
Tell the user:
- The new worktree's absolute path and branch name
- Setup hook result: ran (one-line summary of what it did), or "no
.claude/worktree-setup.sh found" plus the scaffold offer (step 4)
- Suggested next move: open a new Claude Code session at that path (
cd <path> && claude), or cd for shell work
- The current session stays in the original directory โ switching an agent's working dir mid-session is fragile. Pause this session if the new work supersedes it.
Mode: list
Trigger: first arg is exactly list.
- Run
git worktree list --porcelain and parse the worktree <path> / HEAD <sha> / branch <ref> triples (blank-line separated).
- Read
$JOURNAL. For each active entry, capture description + start date.
- For each worktree, also fetch last commit relative time (
git -C <path> log -1 --format='%cr') and uncommitted-changes count (git -C <path> status --porcelain | wc -l).
- Print a table:
SLUG BRANCH LAST COMMIT WIP DESCRIPTION
feature-alpha feature-alpha 2 hours ago 0 Add the alpha feature
bugfix-beta bugfix-beta 12 min ago 1 Fix the beta bug
(main) docs-index 30 min ago 0 โ
Mark the current session's worktree with a * prefix. Journal entries with no matching worktree are orphans โ list them under "Stale journal entries" and suggest /branch done <slug> to close out.
Mode: done
Trigger: /branch done <slug>.
- Find the worktree for
<slug>:
git worktree list --porcelain | awk -v s="<slug>" '/^worktree/{p=$2} /^branch/ && $2=="refs/heads/"s {print p; exit}'
No match โ ask whether the user wants to just close the journal entry (orphan cleanup) and skip git worktree remove.
- Check the worktree's state: uncommitted changes (
git -C <path> status --porcelain non-empty)? Refuse without --force and report what's dirty. Branch unmerged to its base (git -C <path> log <base>..HEAD --oneline non-empty)? Warn "Branch has N unmerged commits. Has the work been pushed and merged via PR?" and let the user proceed if they confirm.
- Ask the user for an outcome line: "What was the outcome? (shipped / abandoned / merged-as-PR-#NNN / etc.)"
- Update the journal: move the entry from
## Active to ## Done; append - **Closed:** <YYYY-MM-DD HH:MM> and - **Outcome:** <outcome>; change status to done.
- Run
git worktree remove <path> (add --force if uncommitted changes were present and the user passed --force).
- Suggest a branch-cleanup command but do not run it โ branch deletion is destructive, the user runs it themselves: merged โ
git branch -d <slug>; abandoned โ git branch -D <slug>; pushed โ also git push origin --delete <slug> after the local delete.
Mode: update
Trigger: /branch update. Heartbeats the current worktree's Active entry โ each invocation bumps Last update to now and sets/clears any provided fields.
Resolve the slug: use --slug <slug> if given, otherwise infer from the current working dir:
CWD_REPO=$(git rev-parse --show-toplevel)
SLUG=$(git rev-parse --abbrev-ref HEAD)
If the slug isn't a ## Active entry in $JOURNAL (e.g. user is on main), refuse: "Not on a worktree branch โ use --slug explicitly or cd into the worktree first."
Field semantics:
--working-on "..." โ replaces Working on:. Empty string "" resets to (idle).
--state <state> โ sets State:, one of building / in-review / blocked / almost-done / stale. Reject other values with a one-line list of valid states. (blocked should usually pair with --blocked-by.)
--next "..." โ sets Next action: (the paste-to-resume line a cold reader follows). Empty string "" resets to (idle).
--blocked-by "..." โ sets Blocked by:. Empty string removes the line.
--asks "..." โ sets Asks:. Empty string removes the line; for multiple asks join with ; or call multiple times (each call replaces).
--dont-touch "..." โ sets Don't touch:. Empty string removes the line.
No field flags given โ only Last update is bumped (a "still alive" ping).
Apply and output: edit the entry in place under ## Active, fields in canonical order (see Journal schema below). Use date '+%Y-%m-%d %H:%M' for the timestamp. Do not auto-commit โ the journal change goes in the next batch. Then print the updated entry's relevant lines to confirm. One-liner: Updated <slug>: <field>=<value>. Last update: <timestamp>.
Mode: coord
Trigger: /branch coord <message> or /branch coord clear <slug>.
Sub-mode: post
/branch coord <message> (where <message> is everything after the coord keyword).
- Resolve current slug (same logic as update mode). If on
main / not in a worktree, the slug is (main).
- Open
$JOURNAL. If ## Coordination is missing, create it between ## Active and ## Done using the template from create-mode step 5.
- If the section's only content is
- (none active), replace it with the new entry; otherwise append. Entry format: - <YYYY-MM-DD HH:MM> โ <slug>: <message>
- Echo:
Posted to ## Coordination as <slug>: <message>. Other sessions will see this on their next prompt.
Sub-mode: clear
/branch coord clear <slug>.
- Open the journal. Remove every line in
## Coordination whose source is <slug> (matches โ <slug>:).
- If the section becomes empty, restore
- (none active).
- Echo:
Cleared <N> coord lines from <slug>.
Do not auto-commit.
Edge cases
Journal schema (v2 โ 2026-04-30)
Branch names are the slug itself (no wt/ prefix). Cross-repo journal merging is out of scope โ each repo's journal stands alone.
Active entries carry these fields, in canonical order:
| Field | Required | Mutable | Set by |
|---|
| Started | yes | no | create |
| Branch | yes | no | create |
| Worktree | yes | no | create |
| Base | yes | no | create |
| Description | yes | no | create โ the immutable charter |
| Working on | yes | yes | create ((starting)), update โ the heartbeat: "what are you doing right now" |
| State | no | yes | create (building), update |
| Next action | no | yes | create ((starting)), update โ the paste-to-resume line |
| Last update | yes | yes | create, update, coord |
| Blocked by | no | yes | update |
| Don't touch | no | yes | update |
| Asks | no | yes | update |
| Status | yes | yes | create (active), done |
## Coordination, between ## Active and ## Done, holds cross-cutting constraints (see Mode: coord).
Migrating a v1 entry to v2: add Working on: (idle) and Last update: <last commit time on the branch>. Don't backfill Blocked by/Don't touch/Asks unless you have current info.