Expand description
Git-worktree helpers for the worker spawn and despawn paths: detecting a repo, reading a worktree’s branch, deciding whether a worktree is safe to remove, removing it, and reaping the branch behind it.
Synchronous throughout because each answer is one fast git subprocess and the callers are already on a tokio task.
Re-exports§
use std::collections::HashMap;🔒use std::io::Write;🔒use std::path::Path;🔒use std::path::PathBuf;🔒use std::process::Command;🔒use std::sync::OnceLock;🔒use super::git_command;🔒
Structs§
- Agent
Worktree - A completed
isolation: "worktree"subagent’s managed worktree, as the Agent tool reports it on completion. Produced only bycompleted_agent_worktree, which vouches for every field. - Listed
Worktree 🔒 - One
git worktree list --porcelainentry. - Recorded
Worktree 🔒 - One record, as
recorded_worktreereads it. - Repo
Branches - What a repo knows about its branches, in two quantities that must not be confused for each other.
- Worktree
Lock 🔒 - A worktree lock: the canonicalized path plus the pid the CLI stamps into its reason, when it carries one.
Enums§
- Agent
Worktree Reap - Outcome of
reap_agent_worktree. - Branch
Reap Outcome - Outcome of
reap_worktree_branch.Kept,KeptOnErrorandDeleteFailedeach leave a branch behind and the caller warns differently for each;NotPresentwarns about nothing. - Worktree
Ensure - What
ensure_worker_worktreehad to do, so a caller rolling back a refused spawn knows what it may delete: the worktree only when it was absent, the branch only when this call minted it. - Worktree
Error - Errors from
remove_worktree. - Worktree
Record 🔒 - What forge recorded about a worktree it owns, in the repo’s git common dir so the record moves with the repo.
- Worktree
Registration 🔒 - Where the worktree at
pathstands with git’s registrations.
Constants§
Functions§
- agent_
worktree_ lock_ is_ live - Whether
path’s lock, re-read now, belongs to a live process. The sweep re-runs this immediately before each removal so a lock taken after the listing still stops that reap. Unlocked or dead-owner trees answer false; unreadable answers true. - branch_
ref_ exists - Whether
branchresolves atrepo, as a local head or as any remote-tracking ref. A repo git cannot read answerstrue, so a caller cleaning up on absence never discards state it failed to inspect. - completed_
agent_ worktree - The agent worktree a completed Agent-tool call left behind, from the PostToolUse hook payload.
- detached_
unique_ 🔒tip - The full sha of a detached worktree HEAD holding commits that only
the worktree itself reaches, or
Nonewhen removal cannot strand anything. An attached branch is judged after removal byreap_worktree_branch; a detached HEAD must be judged before. - discard_
worker_ worktree - Undo the work
ensure_worker_worktreedid for a spawn that was then refused, so the refusal strands neither worktree nor branch. The worktree goes only when ensure created it, the branch only when ensure minted it - anAttachedExistingBranchkeeps the branch, which predates the call and may hold the worker’s only copy of its commits. Returns git’s complaint when something had to stay behind; the spawn is already refused, so a failure is a warning, not an error. - ensure_
worker_ worktree - Recreate the worker worktree at
pathwhen a despawn removed it, so a resumed worker can run with its worktree as cwd again - the transcript lives under the worktree’s storage key, soclaude --resumecannot find it from any other directory. Anchored on the branch: attach the survivingworktree-<label>when despawn kept it, otherwise recreate the branch fromorigin/<default-branch>- the base claude’s own--worktree <label>uses under its defaultworktree.baseRefoffresh. - find_
string_ 🔒field - First string value under
keyin a pre-order walk. The walk is depth-capped at 6: the captured shape is flat and top-level, so a deeper field is treated as absent (surfaced by the caller’s parse-miss log, never guessed at). String content is never searched, so a result text quotingworktreePath:cannot aim the reap. - first_
unresolved 🔒 - True the first time this process reports
rootas unresolved, so the warning is one line per root rather than one per render-path call. - foreign_
leftover_ 🔒detail - Why the directory at
pathis not this repo’s leftover, when its.gitentry says so -Nonewhen it is ours to delete. - git_
common_ 🔒dir - The repo’s git common dir, resolved and canonicalised. A project
checked out as a linked worktree or a submodule answers with the dir
its gitdir lives under, not
<repo>/.git. - git_
config_ 🔒value - One
git config --getvalue,Nonewhen unset or unreadable. - git_
error 🔒 - git’s stderr, or its exit status when it said nothing.
- git_
in_ 🔒repo - Run
git -C repo <args>with the ambient repo-location env scrubbed, returning stdout on exit 0. - is_
git_ repo - Check whether
pathis inside a git repository. Shells out togit rev-parse --git-dir. Exit 0 -> true; anything else -> false. - is_
isolated_ agent_ call - Whether this hook payload names an isolation:“worktree” Agent call - the population whose completion either names a managed worktree or proves the CLI’s tool-response contract drifted.
- listed_
worktrees 🔒 - The entries of a porcelain listing: one block each, a
worktree <path>header followed by thebranchline when the entry holds one. - live_
foreign_ 🔒git_ dir - Why the
.gitlink atpathnames a LIVE git dir that is not this repo’s, when it does;Nonewhen it names this repo’s common dir or the git dir is gone. A stale or absent target is not proof of foreign ownership on its own, so callers weigh it separately. - lock_
pid_ 🔒is_ live - Whether a lock blocks the sweep: a parsable pid whose process is alive blocks, and so does one we cannot judge (no pid, or liveness unanswerable); only a provably dead pid releases the tree.
- locked_
worktrees 🔒 - This repo’s worktrees git reports as locked, canonicalized, with the
pid parsed from the reason when there is one.
Nonewhen git cannot be asked - the caller sweeps as empty rather than judge without the lock facts. - moved_
registration_ 🔒names - Whether a listed worktree path that no longer resolves still names the
worktree at
ours. A repo moved or renamed leaves its registrations carrying the old absolute path, while the tail below the repo root -.claude/worktrees/<label>- is intact. The listing is this repo’s own, so a matching tail is this repo’s registration, not a foreign one. - obstacle_
from_ 🔒listing - The obstacle the listing describes: the branch held at another path, or
what git has registered at
pathand will not reuse. - origin_
base 🔒 - The ref claude’s
--worktreecuts a fresh branch from:origin/<default-branch>as the repo’sorigin/HEADnames it. - parse_
lock_ 🔒pid - The pid in a CLI lock reason,
claude agent agent-<hex> (pid 33497 start ...).Nonefor a barelockedline or any foreign reason. - path_
is_ 🔒taken - Whether something at
pathis in the way ofgit worktree add, which takes a path that is absent or an empty directory and refuses anything else: a file, and any directory holding an entry, both exit 128 (“already exists”). A dotfile counts as an entry - measured. - process_
is_ 🔒live - Whether
pidnames a live process. Unanswerable counts as live: a liveness question we cannot ask never deletes a tree. - reap_
agent_ worktree - Remove a completed subagent’s managed worktree, then reap the
conventional branch behind it. Refuses anything but a porcelain-clean
tree: the measured leak is clean trees whose gitignored target/ the
CLI’s auto-clean does not count, while uncommitted edits are a tree
the CLI handed back deliberately. Only
branchis ever aimed at - a branch the subagent checked out itself is left to git’s normal lifecycle, the same rule the worker despawn applies. - reap_
worktree_ branch - Delete
branchfrom the repo atrepoif no commit would become unreachable, i.e. every commit reachable from it is also reachable from another ref or from a worktree’s HEAD. - record_
worktree_ gone - Record that the worktree at
pathis gone. Retracts any earlier admission for the label, so a directory that later takes its place is not cleared on it. - record_
worktree_ 🔒owned - Record that forge created the worktree at
path. - record_
worktree_ 🔒state - Append one state for
labelin this checkout. Append-only with last line winning: a rewrite would race another spawn’s record in the same process, and the file is a handful of bytes per worker lifecycle. The checkout root is stored with the path because the common dir can be shared by several checkouts. - recorded_
names 🔒 - Whether a recorded path that still exists names the worktree at
current. - recorded_
root_ 🔒is_ ours - Whether a record written by the checkout at
recordedcan speak forrepo: a root that no longer exists is the one a repo move left, while a live root must be this checkout. - recorded_
worktree 🔒 - The record that speaks for the worktree at
path, when one can be read. - remove_
orphaned_ worktree_ dir - Delete the directory a failed
remove_worktreeleft behind, once git no longer lists it as a worktree. - remove_
owned_ worktree - Remove a worktree whose removal this repo admitted - a despawn, or a
refused spawn’s rollback - recording the admission before git runs.
A removal that dies part-way leaves the record behind, which is what
lets
remove_orphaned_worktree_dirprove the leftover is ours after a repo move; a completed removal clears it. - remove_
worktree - Remove the git worktree at
pathviagit worktree remove. Run from the worktree’s PARENT (which lives inside the main worktree for claude’s<repo>/.claude/worktrees/<label>layout), since git refuses to remove the worktree it is invoked from.forceadds--force, removing even a dirty worktree (the despawn path only reaches this on a verified-clean worktree, or when the caller explicitly passedforce). - repair_
worktree 🔒 git worktree repair <path>: git’s own remedy for a registration and link that name the path the repo has left. Only a stale registration needs it; reusing one unrepaired leaves every git command in the worktree failing.- repo_
branch_ names - Every branch name the repo rooted exactly at
repoknows, plus how many branch refs it actually holds.Nonewhenrepois not itself a work-tree root or git could not be asked. - report_
unresolved_ 🔒root - The one-line report for a root git would not name, so a log grep for
worktree_base_unresolvedfinds the path that composed the wrong way. - resolve_
worktree_ 🔒base worktree_basewithout the memo;Nonewhen git could not answer.- same_
path 🔒 - Whether a path git printed and a path forge composed name the same
directory. Each side canonicalizes through its parent with the name
re-attached, so a symlinked spelling of a repo root (a macOS tempdir
under
/private, a linked checkout) cannot read as a different worktree. That matters most for a path that is gone, which is exactly where a stranded registration lives and nothing exists to canonicalize. - scrubbed_
git 🔒 - The command
git_in_reporuns, built separately so a test can assert the scrub is on it. - spelled_
from_ 🔒parent pathwith its parent canonicalized and its own name re-attached: the spelling a path keeps once it exists, for one that does not.- stale_
agent_ worktrees - Managed agent worktrees under
<base>/.claude/worktrees/untouched for at leastage, for the reconciliation sweep that runs at every session spawn: it reaps what a one-shot failure or a missed completion event left behind. A lock whose reason carries a live pid (the CLI stamps one on a live isolation agent’s tree) keeps the tree out; a dead pid - a crashed CLI’s leftover, which git never prunes on its own - is reapable. Locks without a parsable pid are not ours to judge and skip.Ok(vec![])when the worktrees dir is absent; other read errors propagate, and a repo git cannot answer about sweeps as empty - the fail-closed direction for a deleter. Feed the result toreap_agent_worktree. - survivor_
tips 🔒 - Every tip that would survive
worktree_path’s removal: all refs plus the HEAD of each other worktree.Nonewhen git cannot be asked, which the caller treats as keep. - unpushed_
commit_ 🔒count - Commits HEAD reaches that no remote-tracking ref does, with a branch
whose remote branch was deleted counted as pushed.
Nonewhen the repo holds no remote-tracking refs to compare against, or when the query fails - both leave the verdict clean. - upstream_
ref_ 🔒deleted - Whether the branch’s configured upstream names a remote ref that no longer exists.
- worker_
root - The tree a worker spawned at
cwdruns in: the repo root when the path is inside a repository, the path itself otherwise. Both are canonical, because theclaudeCLI derives the session JSONL’s directory from the canonical path - measured: a worktree under/tmpwrites its transcript under/private/tmp- so a record holding the alias would name a directory the CLI never wrote to. - worktree_
base - The checkout root the
claudeCLI forks a worker’s--worktreeunder: the repository’s main working tree, resolved from the first entry ofgit worktree list --porcelain- the main checkout when the project is itself a linked worktree, the repo root for a subdirectory. Measured on 2.1.280: the CLI resolves its cwd to that root before composing<root>/.claude/worktrees/<label>, so a project rooted in a linked worktree (or in a repo subdirectory) otherwise names a directory the CLI never creates (#2460). A path that is not a repository is its own base. - worktree_
branch - The branch checked out at
path, viagit rev-parse --abbrev-ref HEAD.Noneon detached HEAD, a git error, or non-utf8 output. Read before a worktree is removed so its per-branch review threads clean up under the same(project, branch)key the overlay saved them with. - worktree_
creation_ obstacle - What git refuses to create a worker’s worktree for at
path, when there is something: theworktree-<label>branch already checked out in another worktree, a path taken by something that is not this worktree, or a registration git will not reuse. - worktree_
dirty_ reason - Why a worktree is not safe to remove, or
Nonewhen it is clean. - worktree_
gitdir_ 🔒resolves - Whether the worktree registered at
pathstill points at a gitdir git can reuse. - worktree_
missing_ reason - Why the worktree a git worker’s spawn put it in is not usable, or
Nonewhen it is there and git can read it. - worktree_
record_ 🔒path - worktree_
registered 🔒 - Whether git still lists
pathas one ofrepo’s worktrees, live or stale. A repo git cannot read answerstrue, the refusing direction. - worktree_
registration 🔒 - The registration
worktree_registrationfinds, orNonewhen git could not be asked. The clear treats that as listed (the refusing direction); ensure reuses the path as it stands. - worktree_
removal_ 🔒admitted - Whether an admitted removal is on record for this worktree and never reported complete. Where the link cannot prove ownership, this record is what the orphan clear deletes on; a live link naming this repo’s common dir clears without it.
- worktree_
uncommitted_ 🔒reason - Uncommitted or untracked changes, or
Nonewhen the tree is porcelain-clean.