Worktree Management
bash.agents/skills/worktree/scripts/worktree <command> [options]
Worktrees live at <parent-of-checkout>/.worktrees/<checkout-name>/{id}, outside the repo root. Every command's contract is its --help: flags, exit codes, failure semantics, recovery. The top-level worktree --help carries the command index, path and issue-ID rules, configuration variables, and setup-path hardening.
Commands
| Command | Description |
|---|---|
create | Claim a new issue worktree, a new-work claim, not a discovery command: existing ownership exits 75, and owned work is inspected or monitored, never given a second implementer. Reuse and conflict recovery: create --help |
restack | Guardedly continue, skip, or abort a tool-created paused restack |
list | List all worktrees |
remove | Remove worktree, clean symlinks, prune branches |
cleanup | Remove worktrees whose branches are merged; --targets-only prunes build output instead, keeping every worktree and branch |
path / exists | Print / check the worktree path for an issue ID |
merged | Print the commit the issue tree's pull request merged as, asking about the branch that tree has checked out; exit 1 when none did, 2 when the lookup could not answer, a detached tree included (merged --help) |
check | Pre-create git state check (JSON: uncommitted, unpushed) |
push | Push worktree branch with auto-rebase and pinned --force-with-lease; the rebase-map: contract for remapping pre-rebase SHAs is in push --help |
fix-links / repair-links | Restore configured symlinks; repair-links is the git-hook-driven variant that never destroys untracked data |
codex-setup / codex-branch / codex-cleanup, claude-setup / claude-cleanup | App-created worktree hooks. Installation wiring: references/hooks.md |
Policy-blocked rebase (cherry-pick replay fallback)
When an execution policy rejects top-level git rebase porcelain, never retry the porcelain and never substitute a raw --force push. Add --replay to the guarded restack (create --help); the controls stay restack continue|skip|abort <ID>.
A branch is rebased only through worktree push, create --restack, or create --reuse, never a bare git rebase; use this section's replay fallback for recovery.
A branch whose pull request the merge lookup confirms merged is not rebased by create. A squash merge rewrites the branch into a fresh commit, so a rebase replays the merged work onto its own squash and stops on conflicts: create --reuse keeps the tree as it stands, and create --restack and create --replay refuse, all three naming the merge commit. When the lookup cannot answer, create records worktree-merge-unverified and rebases as for a branch in flight (merged --help).
Recovering a broken .agents entry
Route by shape, not by whether test -L .agents passes. Ask both indexes what sits under the path: git -C <worktree> ls-files -- '.agents/', and the same command against the main checkout. The trailing slash is the query; descendants decide the layout and the entry itself does not count. Neither index alone decides, and both answer while the path itself is broken:
- Either non-empty. The repo commits its render, and
.agentsis a REAL DIRECTORY by design: the tracked files, plus one symlink per untracked child, except an untracked.gitignore, which is a copy of main's file. A child missing its link, or a real path where a link belongs, isfix-links. A modified or corrupt TRACKED file isgit checkout -- <path>, run in the checkout the file really lives in: the main checkout when the path sits under a configured symlink, the worktree otherwise. - Both empty. The entry is untracked-only and must itself be a symlink.
fix-linksis the repair; with no tracked content at the path,git checkout -- .agentschanges nothing while the link stays broken.
Run .agents/skills/worktree/scripts/worktree fix-links <ID|PATH> from the main checkout naming the target; a bare invocation there is refused. A non-zero exit names the paths it did not restore, and until they are restored that tree is not trustworthy for local verification. Routing table and link mechanics: fix-links --help.
A consumer wanting this file locally gets a pointer, never a copy: cat "$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")"/.agents/skills/worktree/SKILL.md resolves the main checkout from any worktree, at any depth. A verbatim copy in a tracked AGENTS.md or CLAUDE.md is out of kendex refresh's reach and goes stale silently.
Session guard (ownership leases)
scripts/worktree-session-guard stops cleanup from destroying a claimed worktree, using a native Git worktree lock whose reason line carries the owner and a heartbeat. Who claims and when, what staleness measures, and the guard's limits: references/session-guard.md; commands, exit codes and --repo scope: worktree-session-guard --help.
Reclaiming build output
cleanup --targets-only prunes build output and keeps the worktree, its branch and every tracked and untracked source file. It runs on a worktree with uncommitted work: output is written by a compiler or a package manager, so uncommitted work is no reason to leave it on disk, and on a machine hosting many worktrees the trees holding the output are the ones still in use. It previews by default and deletes only under --apply, and wherever it cannot establish that a path is safe to remove it keeps that path and says why. Run it from the main checkout, read the preview, then repeat with --apply.
The layout table is data, one row per ecosystem, in scripts/worktree-output-prune; covering a further ecosystem is a new row there and no other change. cleanup --help owns everything else: the flags, the layouts, the locking, what the walk excludes, every reason a path is kept, and the recovery for an --apply that did not finish.
JS Dependencies
worktree --help § Dependencies owns install and linked-node_modules behavior.
System Dependencies
git; authenticated gh for new-work PR ownership discovery and for proving a squash-merged branch merged in cleanup and remove; flock for repository-local per-issue claim serialization; Bash 3.2+ (macOS system bash is supported).
Configuration
Set non-sensitive defaults in committed kendex.settings.toml under [env]; .env.local wins for secrets or personal overrides, and a .env file is never read. Symlink only what git does not carry. An entry does nothing when git carries every path under it, and a directory holding tracked content stays a real directory with its untracked children linked, bar an untracked .gitignore, which is copied (fix-links --help). Variable semantics and setup-path hardening: worktree --help.

