Repository navigation
Releases: yoiailabs/rabi
Release list
rabi v7.0.0
rabi (ラビ, from "rabbit") v7.0.0 is the first stable-tagged release of the Rust rewrite and the first v7 release published as the repository's Latest release. It contains no functional changes from v7.0.0-alpha.11. It is not published to npm, crates.io or Homebrew.
Important
v7 is a beta. rabi is under active development. During the v7 beta, minor releases (7.x) may contain breaking changes, the same rule as semver 0.x. Patch releases (7.x.y) contain fixes only. Every breaking change is listed in CHANGELOG. Pin a version for agents and CI.
Install
curl -fsSL https://github.com/yoiailabs/rabi/releases/latest/download/install.sh | shNo --prerelease and no version pin are needed any more: the unpinned installer picks the newest stable v7 release. Add sh -s -- --verify-attestation (needs gh) to also verify GitHub artifact attestations. The installer always verifies SHA-256 against SHA256SUMS, installs rabi to ~/.local/bin without sudo, and replaces it atomically. Linux binaries are static musl builds (Alpine included); macOS x64/arm64 is supported; use the Linux binaries in WSL.
Pinned (agents and CI):
curl -fsSL https://github.com/yoiailabs/rabi/releases/download/v7.0.0/install.sh \
| RABI_VERSION=v7.0.0 sh -s -- --verify-attestationFrom source: cargo install --git https://github.com/yoiailabs/rabi --tag v7.0.0 --locked rabi.
Changes
- v7.0.0 is published as a stable release and becomes Latest; v7 enters beta with the versioning rule above.
- Docs describe the beta and the unpinned install (#902).
- No functional changes since v7.0.0-alpha.11; paths, config and records are unchanged.
Upgrading
- From v7.0.0-alpha.10 or alpha.11: run the install command above. No data migration is needed.
- From older v7 alphas (mabo / Maboroshi / ptm): see the v7.0.0-alpha.10 notes in CHANGELOG; there is no automatic migration.
- From phantom v6 (npm
@phantompane/cli, Homebrewphantom): v7 is a separate Rust rewrite under a new command name (rabi) with its own commands, config and data layout; it does not read v6 data. v6 keeps working and is not removed or updated by this release. npm@phantompane/cliand Homebrewphantomstay on v6.3.2.
rabi v7.0.0-alpha.11
rabi (ラビ, from "rabbit") v7.0.0-alpha.11 is a prerelease of the Rust rewrite. The repository moved to yoiailabs/rabi; old phantompane/phantom URLs redirect. There are no functional changes from alpha.10. It is not published to npm, crates.io or Homebrew.
- This is an alpha. Commands, flags, paths and the JSON schema may still change before 7.0.0, without backward compatibility.
- This is the first release built and attested in
yoiailabs/rabi.
Install
curl -fsSL https://github.com/yoiailabs/rabi/releases/download/v7.0.0-alpha.11/install.sh \
| RABI_VERSION=v7.0.0-alpha.11 sh -s -- --verify-attestation--verify-attestation needs gh. The installer always verifies SHA-256 against SHA256SUMS, installs rabi to ~/.local/bin and replaces it atomically. Linux binaries are static musl builds (Alpine included). From source: cargo install --git https://github.com/yoiailabs/rabi --tag v7.0.0-alpha.11 --locked rabi.
Changes
- The repository is now
yoiailabs/rabi(#900).install.shdownloads from and verifies attestations againstyoiailabs/rabi; docs, help examples andCargo.toml(repository) point there. - Attestations for v7.0.0-alpha.10 and earlier stay with the old organization. The alpha.10 installer's
--verify-attestationno longer works; install alpha.10 without it, or verify by hand withgh attestation verify <file> --owner phantompane.
Upgrading from alpha.10
Run the install command above. No data migration is needed: paths, config and records are the same as alpha.10.
rabi v7.0.0-alpha.10
rabi (ラビ, from "rabbit") v7.0.0-alpha.10 is a prerelease of the Rust rewrite. The product, command, paths, environment variables and release assets are renamed from mabo to rabi. There is no compatibility with mabo: rabi does not read or migrate mabo data. It is not published to npm, crates.io or Homebrew; npm @phantompane/cli and Homebrew phantom remain the TypeScript v6.
Please note:
- This is an alpha. Commands, flags, paths and the JSON schema may still change before 7.0.0.
- No compatibility until 7.0.0. No
maboalias, noMABO_*variables, no.mabo.toml, no reading ofmabo/directories. If you used an earlier alpha, move your data by hand (see below) or recreate your workspaces. - Assets are now
rabi-v<version>-<os>-<arch>.tar.gz, andinstall.shaccepts only that name. Install v7.0.0-alpha.9 (mabo-) or v7.0.0-alpha.8 (maboroshi-) with that release's own installer; v7.0.0-alpha.7 and older have no installer. - The repository is still
phantompane/phantom.
Install
curl -fsSL https://github.com/phantompane/phantom/releases/download/v7.0.0-alpha.10/install.sh | RABI_VERSION=v7.0.0-alpha.10 shOr … | sh -s -- --prerelease for the newest prerelease. Add --verify-attestation (needs gh) to check the GitHub artifact attestation as well. The installer needs curl, installs rabi to ~/.local/bin, always verifies SHA-256 against SHA256SUMS, and replaces the binary atomically. Linux binaries are static musl builds (Alpine included). From source: cargo install --git https://github.com/phantompane/phantom --tag v7.0.0-alpha.10 --locked rabi.
Changes
- Renamed to rabi (#896): the binary and Cargo package are
rabi, help, completions andshell-init(rabi cd) use rabi, and release archives arerabi-v…. - rabi paths only: config
~/.config/rabi, state~/.local/state/rabi, per-repository records<git-common-dir>/rabi/, new workspaces~/.local/share/rabi/workspaces/<repo>-<hash>/<name>(XDG variables respected), repository config.rabi.toml. - Environment variables are
RABI_*(RABI_OUTPUT,RABI_NO_INPUT,RABI_TRUST_ALL,RABI_WORKSPACE,RABI_WORKSPACE_PATH,RABI_REPO_PATH,RABI_WORKSPACE_ID; installerRABI_VERSION,RABI_INSTALL_DIR, …). The child-process marker isRABI=1. - Behavior is otherwise unchanged from alpha.9: commands, flags, JSON (schema 2), exit codes, hooks, trust and safety checks.
- Docs: rebuilt README with reference pages under
docs/(#887, #888); development moved tomain(#895).
Moving data from mabo (alpha.9) by hand
This is what we did on our own machines. Stop all mabo processes first and back up.
mv ~/.config/mabo ~/.config/rabi # if it exists
mv ~/.local/state/mabo ~/.local/state/rabi # if it exists
# In each repository (repeat per repo):
mv "$(git rev-parse --git-common-dir)/mabo" "$(git rev-parse --git-common-dir)/rabi"
[ -f .mabo.toml ] && git mv .mabo.toml .rabi.toml # repository config, if you have one- If
$XDG_CONFIG_HOMEor$XDG_STATE_HOMEis set, move themabodirectory there instead. - Rename
MABO_*environment variables in scripts, hooks and shell profiles toRABI_*, and switch shell integration torabi completions …/rabi shell-init …. - Leave existing checkouts where they are (for example under
~/.local/share/mabo/workspaces/). Their moved records still point at them, so they stay managed; new workspaces go under~/.local/share/rabi/workspaces/. Do not move checkout directories withmv; Git would lose track of them. - If
<git-common-dir>/rabialready exists, merge by hand: copy any missingworkspaces/ws_*.jsonrecords; never overwrite. - Checkouts without a record still appear in
rabi listas unmanaged Git worktrees;rabi adopt PATHrecords them.
mabo v7.0.0-alpha.9
mabo (Maboroshi, 幻) v7.0.0-alpha.9 is a prerelease of the Rust rewrite. The product, its paths and its release assets are now all named mabo, and all backward compatibility with earlier alphas was removed. It is not published to npm; npm @phantompane/cli and Homebrew phantom remain the TypeScript v6.
Please note:
- This is an alpha. Commands, flags, paths and the JSON schema may still change before 7.0.0.
- No compatibility until 7.0.0. mabo does not read or migrate data from earlier alphas, and removed commands, flags and keys fail as ordinary unknown input (no migration hints). If you used an earlier alpha, move your data by hand (see below) or recreate your workspaces.
- Assets are now
mabo-v<version>-<os>-<arch>.tar.gz, andinstall.shaccepts only that name. Earlier releases must be installed with their own installer (alpha.8) or by hand (alpha.7 and older). - The repository is still
phantompane/phantom.
Install
curl -fsSL https://github.com/phantompane/phantom/releases/download/v7.0.0-alpha.9/install.sh | MABO_VERSION=v7.0.0-alpha.9 shOr … | sh -s -- --prerelease for the newest prerelease. Add --verify-attestation (needs gh) to check the GitHub artifact attestation as well. The installer needs curl, installs to ~/.local/bin, always verifies SHA-256 against SHA256SUMS, and replaces the binary atomically. Linux binaries are static musl builds (Alpine included).
Changes
- Renamed to mabo. The Cargo package is
mabo(cargo install --git https://github.com/phantompane/phantom --tag v7.0.0-alpha.9 --locked mabo) and release archives aremabo-v…(#879, #880). - mabo paths only. Config
~/.config/mabo, state~/.local/state/mabo, per-repository records<git-common-dir>/mabo/, new workspaces~/.local/share/mabo/workspaces/<repo>-<hash>/<name>(XDG variables respected) (#881, #884). - Compatibility removed (#882–#885):
- no reading or migration of
maboroshi/directories,worktrees.json/projects.jsonstores, or old worktree layouts and markers; - no migration hints for removed commands and flags (
delete,attach,gh,where,meta,preferences,project,trust --yes/--list,-p,--owner,--mine, …); - no renaming of old config keys or
.mabo.tomlhook keys (post-create/pre-deleteare now unknown keys; usesetup/pre-remove); - no special handling of retired environment variables;
install.shno longer falls back tomaboroshi-archives.
- no reading or migration of
- Safety behavior is unchanged: preservation checks before deleting branches, dirty-work protection, sync refusals and hook trust all work as in alpha.8.
Moving data from an earlier alpha by hand
This is what we did on our own machines. Stop all mabo processes first and back up.
mv ~/.config/maboroshi ~/.config/mabo # if it exists
mv ~/.local/state/maboroshi ~/.local/state/mabo # if it exists
# In each repository (repeat per repo):
mv "$(git rev-parse --git-common-dir)/maboroshi" "$(git rev-parse --git-common-dir)/mabo"- If
$XDG_CONFIG_HOMEor$XDG_STATE_HOMEis set, move themaboroshidirectory there instead. - Leave existing checkouts where they are (for example under
~/.local/share/maboroshi/workspaces/). Their moved records still point at them, so they stay managed. Do not move checkout directories withmv; Git would lose track of them. - If
<git-common-dir>/maboalready exists, merge by hand: copy anyworkspaces/ws_*.jsonrecords that are missing; never overwrite. - Records from alpha.6 or older (
worktrees.json,projects.json) are not read. Usemabo adopt PATH --name NAMEfor existing checkouts andmabo repo add PATHfor registrations. - Checkouts without a record still appear in
mabo listas unmanaged Git worktrees.
Maboroshi v7.0.0-alpha.8
Maboroshi (mabo) v7.0.0-alpha.8 is a prerelease of the Rust rewrite. It is not published to npm; npm @phantompane/cli and Homebrew phantom remain the TypeScript v6.
This release changes how mabo is shipped and installed. CLI behavior is unchanged from alpha.7.
Install
Pinned (recommended for agents and CI):
curl -fsSL https://github.com/phantompane/phantom/releases/download/v7.0.0-alpha.8/install.sh | MABO_VERSION=v7.0.0-alpha.8 shThe installer needs curl (Alpine: apk add curl), installs to ~/.local/bin (MABO_INSTALL_DIR to change), always verifies SHA-256 against SHA256SUMS, and replaces the binary atomically. It never prompts, uses sudo or edits shell profiles. Add sh -s -- --verify-attestation to also check the GitHub artifact attestation with gh. Re-run it with a newer tag to update. See the README for unpinned installs and other methods.
Changes
install.sh: new installer, attached to every release. Prereleases install only when pinned or with--prerelease. Every network call has a time limit.- Static musl Linux binaries:
linux-x64andlinux-arm64now run on any distribution, including Alpine and older glibc systems (alpha.7 needed glibc 2.34+). Asset names are unchanged. SHA256SUMS: covers the four archives andinstall.sh.- GitHub artifact attestations for the archives,
install.shandSHA256SUMS. Verify withgh attestation verify <file> --repo phantompane/phantom.
Maboroshi v7.0.0-alpha.7
This alpha is the v8 command redesign of the Rust mabo CLI: Maboroshi is now "workspaces for parallel coding agents", safe by default and explicit when you want otherwise. Almost every command changed. Read the migration table below before upgrading scripts or agent skills.
Please note:
- This is an alpha. Commands, flags, and the JSON schema may still change before 7.0.0.
- Breaking: the command set, flags, JSON output (now envelope schema 2) and exit codes changed. Removed commands and flags (
delete,attach,github/gh,where,meta,preferences,project,sync -f, …) exit 2 with a migration hint instead of acting as aliases. See Migration from alpha.6. - Nothing is deleted on uncertainty.
remove/gcdelete a branch only when it is proven preserved (merged into the base, matching its upstream, or — new — a merged GitHub PR with the same head). Uncommitted work needs--discard-changes; ignored files (target/,node_modules/) are not dirty and go with the workspace. - No locks, no ownership. Internal locks,
MABO_OPERATION_LOCK, owners andMABO_AGENT_IDare gone; bulk commands rely on the dirty / unpreserved / published checks. - This release is not published to npm.
npm installstill gives you the TypeScript v6phantom. Download a prebuiltmabobinary from this release (see Install). - Existing state is migrated automatically: alpha.6 metadata is imported on the first write; old config keys keep working with warnings;
.mabo.tomlpost-create/pre-deletemust be renamed tosetup/pre-remove(old keys fail with a rename hint). - The repository is still
phantompane/phantom.
Install
Prebuilt binaries are attached to this release. Each archive contains a single mabo binary:
| OS | Architecture | Asset |
|---|---|---|
| Linux | x86_64 | maboroshi-v7.0.0-alpha.7-linux-x64.tar.gz |
| Linux | arm64 | maboroshi-v7.0.0-alpha.7-linux-arm64.tar.gz |
| macOS | Apple silicon | maboroshi-v7.0.0-alpha.7-darwin-arm64.tar.gz |
| macOS | Intel | maboroshi-v7.0.0-alpha.7-darwin-x64.tar.gz |
# Pick <os>-<arch> from the table, e.g. linux-x64 or darwin-arm64.
gh release download v7.0.0-alpha.7 --repo phantompane/phantom --pattern 'maboroshi-v7.0.0-alpha.7-linux-x64.tar.gz'
tar -xzf maboroshi-v7.0.0-alpha.7-linux-x64.tar.gz
install -m 755 mabo ~/.local/bin/mabo
mabo --version # mabo v7.0.0-alpha.7+rust (…)On macOS, a binary downloaded with a browser may be quarantined. Run xattr -d com.apple.quarantine ~/.local/bin/mabo if it won't start.
To build from source instead: git clone https://github.com/phantompane/phantom.git && cd phantom && git checkout v7.0.0-alpha.7 && cargo install --path . --locked.
Optional shell helpers (new): eval "$(mabo shell-init bash)" adds mabo cd NAME; eval "$(mabo completions bash)" adds completions (zsh: same with zsh; fish: mabo shell-init fish | source, mabo completions fish | source).
Added
removeandgc --mergednow accept GitHub merged PR evidence after local preservation checks fail. Linked PRs require a verified repository and a merged head equal to or containing the local tip; ancestry uses existing objects only. Partial clones (extensions.partialCloneconfigured or any remote withpromisor=true) require an exact tip match and skip PR object and ancestry probes on every Git version. Unlinked branches require exactly one merged PR with the same head name and exact tip. Bounded, non-promptingghqueries are cached per command; uncertainty keeps the local verdict. Plans use the same checks, and itempreservationReasonplus text details identify merged PR proof. GitHub Enterprise hosts on plain HTTP or a non-default port cannot prove a merged PR yet; their branches are kept.
Breaking changes
This release redesigns the Rust mabo CLI around workspaces. Update scripts written for alpha.6 before upgrading. The installed binary remains mabo.
- Creation is unified under
create --branch,--pr,--issue, and--detach. The oldattach,github/gh, and create-time--execinterfaces are removed.create --prand--issuenow resolve GitHub sources using authenticatedgh. - Use
show,path, andsetfor inspection and intent; userepo,config, andtrust add|remove|listfor the outer commands.-R/--reporeplaces-p/--project. Removed commands and flags exit 2 with migration hints rather than acting as aliases. removereplacesdelete. Working-file loss (--discard-changes) and deleting unpreserved history (--delete-unpreserved-branch) are separate permissions. Branches are deleted only when preservation is proven; otherwise they stay with a warning. Ignored files are clean and are deleted with the checkout, including nested Git repositories inside ignored directories. Populated submodules or retained submodule data still require--discard-changes.syncselects the current workspace by default. Advancing primary is explicit with--main;--allselects every workspace. Auto rebases unpublished work and merges published work;--rewrite-publishedpermits a published rebase without selecting that strategy. Sync checks fetch destinations before any fetch or integration, including in dry runs.- Machine output uses the schema 2 envelope and the shared exit table below. Workspace keys and exported environment variables use workspace vocabulary. Ownership,
lastUsedAt, internal locks, andMABO_OPERATION_LOCKare removed. - Hooks are
[hooks] setupandpre-remove. Copy sources are always the primary checkout.create --no-setupskips copying and hooks; rerun preparation withsetup. State is imported on the first write, and new workspace directories useworkspaces/; existing directories stay in place.
Migration from alpha.6
B marks an incompatible change. “Migrated” describes automatic state/config migration; “new” is additive. Unchanged flags listed under a renamed command still require changing the command name. See README.md for current examples and the full JSON contract.
| Old (alpha.6) | New | Breaking |
|---|---|---|
create [name] |
create [NAME] |
B: flags change |
create --base REF |
create --base REF |
— |
create --owner X / attach --owner / gh checkout --owner |
Removed (record intent with --purpose) |
B |
create --purpose/--pr/--issue |
--purpose still records intent; --pr selects the PR head; --issue looks up the issue, deriving issues/N and its title when used alone |
B: PR/issue flags now resolve GitHub sources through gh |
create --copy-file P |
create --copy-file P (passed through to setup) |
— |
create --no-hooks |
create --no-setup |
B |
create -x/--exec CMD |
create NAME … then exec NAME -- CMD (name the new workspace; a bare exec -- CMD runs in the current checkout) |
B (removed) |
create --json |
--json (envelope schema 2) |
B |
attach B / attach remote/B |
create --branch B / create --branch remote/B |
B |
github checkout N (PR) / gh checkout N |
create --pr N |
B |
github checkout N (issue) |
create --issue N |
B |
github checkout N --base X |
create --issue N --base X |
B |
list |
list (primary checkout included, kind) |
B (output) |
list --no-default |
list --kind workspace |
B |
list --names |
list --names |
— |
list --owner X |
Removed | B |
| (none) | list --all-repos, list --paths, list --kind |
new |
where NAME |
path NAME |
B |
where NAME --json |
show NAME --json |
B |
delete N… |
remove N… |
B |
delete -f |
remove --discard-changes and/or --delete-unpreserved-branch |
B |
delete --current |
remove . |
B |
delete --keep-branch |
remove --keep-branch |
B: command renamed; flag retained |
delete --no-hooks |
remove --no-hooks |
B: command renamed; flag retained |
sync (in the main checkout) |
sync (same as --main) |
— |
sync (in a workspace: also ff'd the default branch) |
sync (that workspace only); sync . --main advances the primary too |
B |
sync N… |
Same | — |
sync --mine |
Removed (sync in the workspace, or name it) |
B |
sync --all (ownership-based scope) |
sync --all (every workspace) |
B |
sync --no-default |
(default) | B |
sync --rebase / --merge |
Same (+ --auto) |
— |
sync --rebase --force |
sync --rebase --rewrite-published |
B |
sync --keep-conflicts |
Same | — |
sync --json (schema:1) |
Envelope schema 2 | B |
gc |
gc (plan) |
— |
gc --apply |
gc --apply |
— |
gc -f/--force |
remove … --discard-changes per target |
B (removed) |
gc --merged / --orphaned |
Same | — |
gc --pr |
gc --closed-pr |
B |
gc --older-than D (lastUsedAt, a criterion) |
gc --older-than D (createdAt, a narrowing filter) |
B (meaning) |
gc --owner X |
Removed | B |
gc --keep-branch / gc --no-hooks |
Same | — |
trust [path] / trust --yes |
trust add [REPO] |
B |
trust --list |
trust list |
B |
untrust [path] |
trust remove [REPO] |
B |
exec NAME CMD… |
exec NAME -- CMD… (-- required, NAME optional) |
B |
meta set N --owner X |
Removed | B |
meta set N --purpose/--pr/--issue/--base |
set N … |
B |
preferences get/set/remove K |
config get/set/unset K |
B |
config keys worktrees-directory / root / ghq-discovery |
workspace-root / clone-root / discover-ghq |
Migrated: old file keys warn and still work; rewritten on the next config write; new names win |
config keys keep-branch / directory-name-separator |
Ignored with warnings and removed on the next config write; use the --keep-branch flag for retirement |
B |
version command / -v |
--version / -v (only the command is dropped) |
B |
clone … |
Same | output only |
| `project add/list/r... |
Maboroshi v7.0.0-alpha.6
This alpha adds mabo sync, which keeps your default branch and worktrees current without risking your work (#850). It also makes attach work with remote branches (#848, closes #776), lets gc and delete recognize squash-merged branches (#847), and fixes github checkout for merged PRs whose branch is gone (#845).
Please note:
- This is an alpha. Commands, flags, and the JSON schema may still change before 7.0.0.
- Breaking:
mabo create <name>now refuses a name that already exists as a remote-tracking branch (exit 3) and tells you to runmabo attach <name>instead. See Remote-aware attach. - Behavior change:
mabo createno longer sets an upstream, even with--base origin/<branch>. Your first push usesgit push -u origin <name>. See Smaller fixes. - This release is not published to npm.
npm installstill gives you the TypeScript v6phantom. Download a prebuiltmabobinary from this release (see Install). - JSON output stays at schema version 1. New fields are additive:
orphanedon worktree objects,basein metadata,github.sourceon checkout.mabo sync --jsonintroduces its own document,"schema": 1. - The repository is still
phantompane/phantom.
Install
Prebuilt binaries are attached to this release. Each archive contains a single mabo binary:
| OS | Architecture | Asset |
|---|---|---|
| Linux | x86_64 | maboroshi-v7.0.0-alpha.6-linux-x64.tar.gz |
| Linux | arm64 | maboroshi-v7.0.0-alpha.6-linux-arm64.tar.gz |
| macOS | Apple silicon | maboroshi-v7.0.0-alpha.6-darwin-arm64.tar.gz |
| macOS | Intel | maboroshi-v7.0.0-alpha.6-darwin-x64.tar.gz |
# Pick <os>-<arch> from the table, e.g. linux-x64 or darwin-arm64.
gh release download v7.0.0-alpha.6 --repo phantompane/phantom --pattern 'maboroshi-v7.0.0-alpha.6-linux-x64.tar.gz'
tar -xzf maboroshi-v7.0.0-alpha.6-linux-x64.tar.gz
install -m 755 mabo ~/.local/bin/mabo
mabo --version # mabo v7.0.0-alpha.6+rust (…)On macOS, a binary downloaded with a browser may be quarantined. Run xattr -d com.apple.quarantine ~/.local/bin/mabo if it won't start.
To build from source instead: git clone https://github.com/phantompane/phantom.git && cd phantom && git checkout v7.0.0-alpha.6 && cargo install --path . --locked.
Upgrading from alpha.5 needs no migration. Existing metadata keeps working: records without a base sync against the default branch.
Highlights
mabo sync (#850)
What's new. One command brings the default branch and your worktrees up to date with their bases. It fetches, fast-forwards the default branch, and rebases or merges each selected worktree onto its recorded base. It never pushes and never leaves a worktree half-integrated.
Why. With several worktrees, and often several agents, staying current meant cd into each one and running git fetch && git rebase origin/main (or merge), then hoping nothing was dirty or already pushed. Rebasing pushed commits rewrites history other people see. A conflict halfway through leaves a worktree mid-rebase. And it's easy to update the wrong branch. mabo sync makes the safe choice for each worktree and skips anything it can't update cleanly.
Which worktrees it touches.
| Invocation | Default branch | Worktrees |
|---|---|---|
mabo sync in the main checkout |
fast-forward | none |
mabo sync in a linked worktree (any subdirectory) |
fast-forward, best effort | the current worktree |
mabo sync <name>… |
fast-forward, best effort | the named worktrees |
mabo sync --all |
fast-forward, best effort | every recorded worktree (see below for agents) |
mabo sync --mine |
fast-forward, best effort | worktrees whose owner is $MABO_AGENT_ID |
add --no-default |
skipped | unchanged |
mabo sync # from the main checkout: just update main
cd "$(mabo where feature/auth)" && mabo sync # main (best effort) + feature/auth
mabo sync feature/auth feature/tests # named worktrees
mabo sync --all --no-default # every worktree, leave main alone
MABO_AGENT_ID=worker-1 mabo sync --mine --json # an agent syncing only its own worktreesmain updated
feature/auth updated
feature/old merged Use mabo gc to remove landed worktrees
feature/wip dirty
feature/api conflict src/api.rs (rebase aborted; worktree unchanged)
Sync complete: 2 updated, 1 merged, 1 dirty, 1 conflict
How each worktree is integrated. The default strategy is auto:
- Rebase when none of the branch's own commits (
base..branch) is on any remote-tracking ref. Local-only history stays linear. - Merge as soon as any of them has been pushed. Published history is never rewritten behind your back; you get a
Merge origin/main into feature/xcommit instead. --mergealways merges.--rebasealways rebases, but on pushed commits it refuses with codepushed(exit 3) unless you also pass--force.
mabo sync feature/x --rebase # feature/x failed Published commits require --force with --rebase (exit 3)
mabo sync feature/x --rebase --force # you've decided to rewrite it; you push with --force-with-lease yourselfSafety guarantees.
- Dirty worktrees are skipped, including untracked files. So is a worktree where the rebase or merge would overwrite an ignored file, for example a local
.envor build output at a path some commit touches. mabo checks every commit it would replay or apply, not just the end states. - Conflicts abort cleanly. On a conflict, even in the middle of a multi-commit rebase, mabo lists the conflicting files and aborts. HEAD, the index, refs, the stash, and file contents end up exactly as they were. Git may refresh the timestamps of files the rebase touched. Want to resolve it yourself? Run
mabo sync <name> --keep-conflictson a single worktree, and the rebase or merge stays in progress. - Skipped instead of touched: detached worktrees (
detached), branches whose work already landed in their base, including squash merges (merged, usemabo gc), and, with--allwhileMABO_AGENT_IDis set, worktrees another agent owns (owned). Naming another agent's worktree explicitly still works. - The default branch only fast-forwards, including when it's checked out in a linked worktree. Local commits on it report
diverged, and blocking changes reportdirty. When you also asked for worktrees, these are best effort and don't stop the worktree updates. - Nothing leaves your machine. sync never pushes, never changes upstream configuration, and runs no hooks.
- One mabo operation at a time. sync holds a repository operation lock for the whole run, so a concurrent
create,attach,delete,gc, orgithub checkoutwaits ("Waiting for another mabo operation to finish…" on stderr). It doesn't wait on.mabo.tomlhooks or--execcommands, because those run after the lock is released. A mabo command started from a Git hook inside another mabo operation (for example, apost-checkouthook) reuses that operation's lock instead of deadlocking. - History is never silently rewritten or dropped. A rebase keeps commits that upstream cherry-picked and then reverted, as well as intentionally empty commits. A local merge commit that carries changes of its own (an "evil merge" or a conflict resolution) isn't flattened: sync refuses it with
merge commit <sha> has changes of its own; use --merge. History with no common base is refused too. Commits count as published when they're on any ref your fetch refspecs write to, not onlyrefs/remotes/*. - A failure stays with its own item. A failed fetch only fails the targets whose base comes from that remote. A default branch that can't be determined only fails the default item. When a run has both an ordinary failure and a published-history refusal, the exit code is 1.
- Broken worktrees stay contained. A worktree whose directory was deleted is skipped as
dirty. A default branch that's mid-rebase or mid-bisect anywhere is skipped asdirty(for example, "main is being rebased in /repo"). Your own Git config, such as--no-commitmerge options,merge.ff=only, orrebase.autoSquash, can't make sync stop halfway or report success without integrating. Sync also disables Git hooks andcore.fsmonitorfor every Git command it runs. - Fresh worktrees stay fresh. A worktree with no commits of its own is simply moved to the new base tip.
gcdoesn't mistake it for merged work afterwards.
Requirements. Sync's rebase needs Git 2.29 or later (--reapply-cherry-picks). Checking merge commits before a rebase needs Git 2.38 or later (git merge-tree --write-tree). With an older Git, a branch that contains merges is refused with a --merge hint instead of being flattened.
Bases. mabo create and mabo github checkout now record each worktree's sync base in metadata: your --base, normalized to a full ref such as refs/remotes/origin/next, or else the remote-tracking default branch. A PR checkout records the PR's target branch (for example refs/remotes/origin/next). Worktrees from older releases have no recorded base and sync against the default branch. To change a base, or to repair one whose branch was deleted (reported as base-gone with a hint):
mabo meta set feature/x --base origin/release-7Exit codes and JSON.
- 0: everything updated, up-to-date, or skipped.
- 1: any
conflictorfailed, or a default-only run that wasdirtyordiverged. - 2: a usage error, such as
--keep-conflictswith several targets or--minewithoutMABO_AGENT_ID. - 3: a refusal to rewrite pushed history, when nothing else failed. Any conflict or failure in the same run makes it 1.
--json prints one document:
{
"schema": 1,
"default": { "name": "main", "...Maboroshi v7.0.0-alpha.5
This alpha gives the tool its new name: Maboroshi (幻), with the command mabo (#841). It also moves worktrees out of your repository into the XDG data directory (#837), and stops delete from throwing away commits that exist nowhere else (#839).
Please note:
- This is an alpha. Commands, flags, and the JSON schema may still change before 7.0.0.
- Breaking: everything named
ptmis nowmaboormaboroshi, and there is no migration. The old binary, config file, env vars, and data directories are not read. See Breaking changes. - Breaking: new worktrees no longer go in
.git/ptm/worktrees. They go under$XDG_DATA_HOME/maboroshi/worktrees/. See Worktrees under the XDG data directory. - There is no compatibility with v6. The TypeScript
phantomonmainand the npm package are unchanged. - This release is not published to npm.
npm installstill gives you the TypeScript v6phantom. To get Maboroshi, download a prebuiltmabobinary from this release. See Install. - JSON output stays at schema version 1. Error codes are unchanged.
- The repository is still
phantompane/phantom. It will move toyoiailabs/maboroshilater.
Install
Prebuilt binaries are attached to this release. Each archive contains a single mabo binary:
| OS | Architecture | Asset |
|---|---|---|
| Linux | x86_64 | maboroshi-v7.0.0-alpha.5-linux-x64.tar.gz |
| Linux | arm64 | maboroshi-v7.0.0-alpha.5-linux-arm64.tar.gz |
| macOS | Apple silicon | maboroshi-v7.0.0-alpha.5-darwin-arm64.tar.gz |
| macOS | Intel | maboroshi-v7.0.0-alpha.5-darwin-x64.tar.gz |
# Pick <os>-<arch> from the table, e.g. linux-x64 or darwin-arm64.
gh release download v7.0.0-alpha.5 --repo phantompane/phantom --pattern 'maboroshi-v7.0.0-alpha.5-linux-x64.tar.gz'
tar -xzf maboroshi-v7.0.0-alpha.5-linux-x64.tar.gz
install -m 755 mabo ~/.local/bin/mabo
mabo --version # mabo v7.0.0-alpha.5+rust (…)On macOS, a binary downloaded with a browser may be quarantined. Run xattr -d com.apple.quarantine ~/.local/bin/mabo if it won't start.
To build from source instead: git clone https://github.com/phantompane/phantom.git && cd phantom && git checkout v7.0.0-alpha.5 && cargo install --path . --locked.
Highlights
Maboroshi (幻) and the mabo command (#841, #842)
Maboroshi is Japanese for a phantom or illusion. It is the same idea as before, written in the project's own language, and it gets the tool away from a crowded English name. The command is the short form, mabo.
$ mabo --version
mabo v7.0.0-alpha.5+rust (…)
$ mabo create feature-auth
Created worktree 'feature-auth' at /home/me/.local/share/maboroshi/worktrees/app-1c941637/feature-auth
$ cd "$(mabo where feature-auth)"The rename is complete, and nothing falls back to the old names:
- Help, errors, and docs all say
mabo. - Hooks and
mabo execexportMABO_*variables. - Data lives in
maboroshidirectories. ptm,PTM_*,.ptm.toml,ptm/directories, and the olderphantomandPHANTOM_*names are all ignored.
Breaking changes (old → new)
| What | alpha.4 | alpha.5 |
|---|---|---|
| Command | ptm |
mabo |
| Cargo package / crate | phantom |
maboroshi |
| Project config | .ptm.toml |
.mabo.toml |
| Hook and exec env flag | PTM=1 |
MABO=1 |
| Hook and exec env vars | PTM_NAME, PTM_PATH, PTM_ROOT_PATH |
MABO_NAME, MABO_PATH, MABO_ROOT_PATH |
| Agent owner | PTM_AGENT_ID |
MABO_AGENT_ID |
| Trust everything for one process | PTM_TRUST_ALL=1 |
MABO_TRUST_ALL=1 |
| User preferences | $XDG_CONFIG_HOME/ptm/config.toml |
$XDG_CONFIG_HOME/maboroshi/config.toml |
| Trust records | $XDG_STATE_HOME/ptm/trust.toml |
$XDG_STATE_HOME/maboroshi/trust.toml |
| Worktree metadata | <git-common-dir>/ptm/worktrees.json |
<git-common-dir>/maboroshi/worktrees.json |
| Default worktree location | <git-common-dir>/ptm/worktrees/<name> |
$XDG_DATA_HOME/maboroshi/worktrees/<checkout>-<hash>/<name> |
| Project marker / lock | — | .maboroshi-common-dir, .maboroshi-worktrees.lock |
| Release asset | ptm-v<version>-<os>-<arch>.tar.gz (contains ptm) |
maboroshi-v<version>-<os>-<arch>.tar.gz (contains mabo) |
Preference keys (worktrees-directory, directory-name-separator, keep-branch, ghq-discovery, root) and JSON error codes are unchanged.
Upgrading from alpha.4
There is no automatic migration. Do these once by hand:
# 1. Install mabo (see Install above). Keep ptm until you no longer need it.
# 2. Rename project config and env vars.
git mv .ptm.toml .mabo.toml # in each repository that has hooks
# PTM_AGENT_ID → MABO_AGENT_ID, PTM_TRUST_ALL → MABO_TRUST_ALL, and $PTM_* in hook scripts → $MABO_*
# 3. Move your preferences, if any.
mkdir -p ~/.config/maboroshi && mv ~/.config/ptm/config.toml ~/.config/maboroshi/
# 4. Re-trust repositories whose hooks you run (trust is not carried over).
mabo trust --yesExisting worktrees made by ptm keep working as ordinary Git worktrees. mabo lists them, under their branch name or their short commit hash when detached, but it doesn't consider them its own:
mabo deleterefuses them. Remove them withgit worktree remove <path>, or finish with them using the old ptm binary.mabo gcnever touches them.
To adopt one, record it with mabo meta set <name> --purpose "…".
Worktrees under the XDG data directory (#837)
What's new. mabo create and mabo attach no longer put worktrees inside your repository's .git. The default location is:
$XDG_DATA_HOME/maboroshi/worktrees/<checkout>-<hash>/<name>
# ~/.local/share/maboroshi/worktrees/... when XDG_DATA_HOME is unset, empty, or relative
<checkout>is the main checkout's directory name (characters outsideA–Z a–z 0–9 . _ -become-).<hash>is the first 8 hex digits of the SHA-256 of the canonical git common directory. Two clones of the same remote get different folders, and a symlinked spelling of the same checkout gets the same folder.
Why.
- Worktrees inside
.gitwere hidden from your file manager and editor. - They got swept up by tools that walk
.git. - They were lost when the clone was deleted.
A single XDG location keeps every project's worktrees in one predictable place, out of the repository, with .git left as Git's own.
How to use it. Nothing to configure. mabo where <name> and mabo list show the paths, and mabo list abbreviates paths under your home with ~ (#840):
$ mabo list
main (~/src/app)
feature-auth (~/.local/share/maboroshi/worktrees/app-1c941637/feature-auth)
$ mabo list --json | jq -r '.worktrees[].path' # always absoluteTo keep worktrees next to the repository instead, set a path relative to the repository root:
mabo preferences set worktrees-directory ../maboroshi-worktreesSafety rules. mabo deletes or garbage-collects only worktrees it owns: those inside the resolved worktrees directory, or recorded in <git-common-dir>/maboroshi/worktrees.json (compared by canonical path).
- The per-project folder is removed only when it is empty and its
.maboroshi-common-dirmarker names this repository. maboroshi/worktreesitself is never removed.- A custom
worktrees-directoryis left in place when it becomes empty.
delete protects unpushed and detached commits (#839, closes #838)
What's new. mabo delete used to delete the branch along with the worktree even if the branch had commits that existed nowhere else. It now refuses unless every commit is on the default branch or on a remote:
$ mabo delete spike
Worktree 'spike' has unpushed commits. Use --keep-branch to keep the branch, or --force to delete it.
$ echo $?
3Why. A worktree is cheap, but an unpushed branch can be hours of an agent's work. Deleting it should be a decision, not a side effect.
How it works.
- Branch worktrees. Commits reachable from
refs/heads/<branch>but not from the default branch (origin/HEAD, elsemain/master) or any remote-tracking ref are "unpushed". The check uses the full ref, so a tag or a remote ref with the same name can't hide them. - Detached worktrees. The check runs on the worktree's
HEADcommit. A detached worktree with a new commit is refused, even with--keep-branch, because there's no branch to keep. - When the check can't run (for example, no default branch can be found), delete refuses rather than guess.
- Options:
--keep-branchdeletes the worktree but keeps the branch, so nothing is lost; it's allowed without--force.--forcedeletes anyway.
- The current directory. Deleting the worktree you are standing in, by name, now needs
--currentor--force. This works from a subdirectory and through symlinked paths (#840). - Several names at once. Every name is checked before anything happens. If any is refused, nothing is deleted and no
pre-deletehook runs. With--json, every refusal is listed. A repeated name, or two names for the same worktree, is handled once (#840).
$ mabo delete done-1 spike done-2 --json # spike is unpushed → nothing deleted, exit 3
$ mabo delete spike --keep-branch # remove the worktree, keep the branch
$ mabo delete spike --force # really delete bothJSON refusals use code unpushed (or uncommitted / validation) with exit code 3. mabo gc uses the same unpushed check, so it now also skips a branch whose only copy is shadowed by a same-named tag.
Smaller fixes
- Recorded worktrees outside the managed directory keep their names (#840). A worktree recorded in metadata is listed and found by its recorded name, for example an older detached
nextworktree, not by its short commit hash.where,exec,delete,meta, andgcall use that same name...
ptm v7.0.0-alpha.4
This alpha adds hook trust: ptm no longer runs a repository's .ptm.toml hooks until you have agreed to trust that project (#833, #835). It also fixes ptm gc so it never collects a worktree that was just created (#832), and makes ptm find the right main checkout in separate-git-dir and submodule layouts (#828, #831).
Please note:
- This is an alpha. Commands, flags, and the JSON schema may still change before 7.0.0.
- Behavior change: a repository that defines
[hooks]in.ptm.tomlnow refusescreate,attach,delete, andgithub checkoutuntil it is trusted, or until you pass--no-hooks. Agents and CI get anuntrustederror instead of a prompt. Repositories without hooks are not affected. See Hook trust. - There is no compatibility with v6.
- This release is not published to npm. The npm package stays on v6. Prebuilt
ptmbinaries for Linux and macOS (x64 and arm64) are attached to this release. - JSON output stays at schema version 1. New fields are additive, and there is one new error code,
untrusted.
Highlights
Hook trust (#833, #835, closes #792, #834)
.ptm.toml hooks are shell commands that come from the repository:
# .ptm.toml
[hooks]
post-create = ["pnpm install"]
pre-delete = ["docker compose down"]Until now, cloning a repository and running ptm create executed its post-create commands with no question asked. That is fine for your own projects, but not for something you just cloned to review, or for an agent that was pointed at an unfamiliar repository. ptm now works like VS Code's Workspace Trust: hooks run only in projects you have trusted.
How trust works
-
Trust is per project, not per command. You trust a repository once, and its main checkout and all its linked worktrees share that decision. Editing the hook commands later does not ask again.
-
Trust is keyed by the repository's git common directory (
git rev-parse --git-common-dir, canonicalized). In a normal clone that is<checkout>/.git; in a separate-git-dir layout it is the git dir. Symlinked or relative paths resolve to the same record.- A second clone of the same remote is a different project and is not trusted automatically.
- Deleting a repository and cloning it again at the same path keeps the old record. Run
ptm untrustfirst if that matters to you.
-
Only hooks are gated.
post-createandpre-deletefrom.ptm.tomlneed trust..worktreeincludecopies,--exec, andptm execare things you ask for explicitly and are not gated. A repository with no hook commands (or an empty[hooks]table) never asks. -
Records live in
$XDG_STATE_HOME/ptm/trust.toml(default~/.local/state/ptm/trust.toml):[[project]] path = "/home/me/app/.git" trustedAt = "2026-10-20T12:37:41.844Z"
What happens in an untrusted project
At a terminal, ptm shows the project and the exact commands, then asks. Only y or yes continues; anything else, including just pressing Enter, refuses:
$ ptm create feature-x
Project: /home/me/app
post-create:
pnpm install
pre-delete:
docker compose down
Trust this project and run its hooks? [y/N] y
Running post-create commands...
Executing: pnpm install
Created worktree 'feature-x' at /home/me/app/.git/ptm/worktrees/feature-xAnswering y saves trust for the project, so the next command doesn't ask. Control characters and invisible Unicode in hook commands (escape sequences, bidi overrides, zero-width characters) are shown escaped, for example \u{1b}, so a hook can't hide what it does in the prompt.
Without a person to answer, ptm never asks. It refuses before it changes anything — no branch, no worktree, no fetch — and exits 1. That applies when:
--jsonis passed,- stdin or stderr is not a terminal,
CIis set, orPTM_AGENT_IDis set (#835). Agents often run under a pseudo-terminal, where a prompt would hang forever. An empty value counts as unset.
$ PTM_AGENT_ID=grok ptm create feature-x
Untrusted project: /home/me/app
post-create:
pnpm install
pre-delete:
docker compose down
Refusing to run project hooks. Run `ptm trust` or pass `--no-hooks`.
$ echo $?
1With --json, stdout is the usual error envelope with the new code untrusted:
{
"version": 1,
"error": {
"message": "Untrusted project: /home/me/app\npost-create:\n pnpm install\npre-delete:\n docker compose down\nRefusing to run project hooks. Run `ptm trust` or pass `--no-hooks`.",
"code": "untrusted",
"exitCode": 1
}
}This covers every command that runs hooks: create, attach, and github checkout (post-create), and delete (pre-delete), including delete --force. github checkout checks trust before it fetches the PR branch.
Managing trust: ptm trust / ptm untrust
$ ptm trust # shows the hooks, then asks "Trust this project? [y/N]"
Trusted /home/me/app/.git
$ ptm trust --yes ~/src/other-repo # no question; required for agents, CI, and --json
$ ptm trust --list
/home/me/app/.git 2026-10-20T12:37:41.844Z
$ ptm untrust
Removed trust for /home/me/app/.gitptm trust [path]andptm untrust [path]take any directory inside the repository; the default is the current directory. A repository with no hooks can still be trusted.ptm trustdoesn't ask where ptm wouldn't prompt (no terminal,--json,CI,PTM_AGENT_ID); it tells you to re-run with--yes.ptm untruston a project that isn't trusted is a no-op that exits 0. Untrust takes effect right away, including for linked worktrees.- All three accept
--json:trust→{"version":1,"path","trustedAt"},untrust→{"version":1,"path","removed"},trust --list→{"version":1,"projects":[{"path","trustedAt"}]}.
Skipping or bypassing hooks
-
--no-hooksoncreate,attach,delete,gc, andgithub checkoutdoes the worktree change and runs no hooks. It does not trust the project.$ ptm create feature-x --no-hooks Created worktree 'feature-x' at /home/me/app/.git/ptm/worktrees/feature-x
-
PTM_TRUST_ALL=1treats every repository as trusted for that one process and writes nothing. It's meant for CI jobs and throwaway containers. The value must be exactly1.
ptm gc and trust
gc never prompts, even at a terminal. In an untrusted project with pre-delete hooks, it skips the candidates it would have removed, with the detail untrusted hooks, and moves on:
$ ptm gc --apply
skip fix-login [merged] untrusted hooks
Removed 0. Skipped 1. Failed 0.
$ ptm gc --apply --no-hooks # remove them without running pre-delete
removed fix-login [merged]
Removed 1. Skipped 0. Failed 0.Setting up agents and CI
- Your own repositories: run
ptm trustonce per repository (orptm trust --yes <path>from a script). Agents working there then run hooks as before. - Repositories you don't control: have agents pass
--no-hooks, or leave them untrusted and handle theuntrustederror code. - CI / containers: set
PTM_TRUST_ALL=1if the job should run hooks.
ptm gc never collects a fresh worktree (#832, closes #826)
alpha.3's gc decided that a worktree was "still at its creation point" by reading the branch reflog. If the reflog was missing (core.logAllRefUpdates=false, or expired entries), a brand-new worktree with no commits counted as merged once the default branch moved ahead, and gc --apply removed it, even when another agent had just created it.
ptm now records the commit a branch started from as createdFrom in the worktree metadata:
ptm create(andgithub checkoutof an issue) stores the new branch's starting commit.ptm attachandgithub checkoutof an existing PR branch store the oldest reflog entry for that branch, ornullwhen there is no reflog.- gc does not treat a branch as merged while its tip still equals
createdFrom. That holds even without a reflog. - Records written before alpha.4 have no
createdFromand use the reflog as before.
createdFrom also appears in the worktree JSON:
"createdAt": "2026-10-20T08:31:32.236Z",
"createdFrom": "38b97374176d03bd95cc65936040b7eeed79b9c7",Merged branches are still collected as before: fast-forward merges, merge commits, and squash merges through a closed or merged PR.
Main checkout in separate-git-dir and submodule layouts (#828, #831, closes #824, #829, #830)
Most repositories keep their git directory at <checkout>/.git. With git init --separate-git-dir, in submodules, and in similar setups, the git directory lives somewhere else, and Git itself reports that directory, not your checkout, as the "main worktree". ptm used to pass that along, which led to several bugs:
ptm delete --currentfailed from inside a linked worktree.- A relative
worktrees-directorynested new worktrees inside the current one. ptm listshowed the git directory as the main entry with"default": false, andptm exec mainran commands inside the git directory.
ptm now finds the main checkout with a fixed set of rules and never guesses:
- If the command is running inside a checkout whose
.gitfile points at the git directory, that checkout is the main checkout. - Otherwise, if
core.worktreeis set, that directory is the main checkout. Submodules set it automatically. If it doesn't point back to the git directory, ptm says so:core.worktree is set to /home/me/nogit, which has no .git gitfile pointing at /home/me/var/sep.git. - Otherwise ptm runs a short, bounded search near the git directory for a checkout whose
.gitfile points at it. If it finds exactly one, that is the main checkout. If it finds several (for example aftercp -rof a checkout) or none, ptm stops with an error that tells you how to fix it:More than one c...
ptm v7.0.0-alpha.3
This alpha helps agents that share a machine keep their worktrees in order. ptm now records who created each worktree and why (#813). It also gains ptm gc to remove the ones nobody needs anymore (#825), and keeps worktrees under .git/ptm/worktrees (#812, #823).
Please note:
- This is an alpha. Commands, flags, and the JSON schema may still change before 7.0.0.
- There is no compatibility with v6, and none was added in this release. Existing worktrees under
.git/phantom/worktreesare not migrated automatically. See Moving existing worktrees. - This release is not published to npm. The npm package stays on v6. Prebuilt
ptmbinaries for Linux and macOS (x64 and arm64) are attached to this release. - JSON output stays at schema version 1. Every new field is additive.
Highlights
ptm gc: clean up stale worktrees safely (#825, closes #775)
Agents create worktrees all the time and rarely clean up after themselves. Over a few days a repository collects dozens of checkouts for branches that were merged long ago, PRs that were closed, and directories that were deleted by hand. ptm gc finds those worktrees and removes them, and it is careful never to throw away work.
gc is a dry run by default. It prints the plan and changes nothing until you pass --apply (or --force):
$ ptm gc
skip docs-typo [merged] dirty
would remove fix-login [merged]
Dry run. 1 would be removed, 1 skipped. Re-run with --apply to remove them.
$ ptm gc --apply
skip docs-typo [merged] dirty
removed fix-login [merged]
Removed 1. Skipped 1. Failed 0.
$ ptm gc --apply
skip docs-typo [merged] dirty
Removed 0. Skipped 1. Failed 0.Each line names the worktree, why it matched (in brackets), and for a skip, why it was kept. Running gc again is safe: a second --apply removes nothing new.
What gc selects
With no criterion flag, gc selects worktrees that are merged, have a closed or merged pull request, or are orphaned. If you pass one or more criterion flags, only those criteria are used, and a worktree matching any of them is a candidate.
| Flag | Selects a worktree when… |
|---|---|
--merged |
its branch is already contained in the default branch. The default branch is origin/HEAD when that ref exists, otherwise main, otherwise master. A branch that still points at the commit it was created from is not merged, so a fresh worktree is never removed just because its branch has no commits yet. |
--pr |
its linked pull request is closed or merged (see Pull request checks). |
--orphaned |
its directory is missing, Git reports the registration as prunable, or its branch ref is gone. A metadata record whose directory is already gone is dropped without touching anything Git does not list. |
--older-than <N><unit> |
it was last used (or created, if never used) at least that long ago. Units are m, h, d, w, for example 14d. Only used when you pass it. |
Two more flags narrow the scope or change what gets deleted:
--owner <id>only considers worktrees recorded for that owner (see worktree metadata). Unowned worktrees are excluded.--keep-branchremoves the worktree but keeps its branch. Thekeep-branchpreference has the same effect.
--project <ref> / -p works as it does for other commands.
Safety rules
gc is meant to run unattended, so its rules lean towards keeping things:
- Never removed, not even with
--force:- the current worktree (where you run gc),
- the primary checkout,
- any worktree checked out on the default branch (its branch is kept too),
- a locked worktree (
git worktree lock).
- Another agent's worktree is removed only when it is merged, its PR is closed or merged, it is orphaned, or
--ownernames that agent. Age alone never removes another agent's worktree. "Another agent" means a worktree recorded with an owner that is notPTM_AGENT_ID. WhenPTM_AGENT_IDis unset, every owned worktree counts as another agent's. - Uncommitted or unpushed work is kept unless you use
--force. Worktrees with modified or untracked files, or whose branch has commits that are on neither the default branch nor any remote-tracking branch, are skipped (dirty,unpushed). If gc can't determine the state, it skips the worktree asstatus unknown. - Branches with unpushed commits survive. When a worktree is removed without
--forceand gc can't confirm its commits are pushed, the branch is kept (kept the branch because it has unpushed commits). - Removal goes through the normal delete path. The
[hooks] pre-deletecommands from.ptm.tomlrun first, then the same deletion asptm delete, including the metadata record. If a hook fails, that worktree is skipped and gc moves on to the next one.
-f, --force implies --apply. It removes matching worktrees even when they are dirty or have unpushed commits, and deletes their branches. It still never touches the protected worktrees listed above.
$ ptm gc --merged --force
skip current [merged] current worktree
removed dirty [merged]
skip locked [merged] locked
removed untracked [merged]
Removed 2. Skipped 2. Failed 0.Pull request checks
The --pr criterion uses the PR link stored in the worktree's metadata, which is set by ptm github checkout, --pr, or ptm meta set --pr:
- A stored URL is passed directly to
gh pr view <url>. A PR in a fork or another repository is checked in that repository, never mixed up with a same-numbered PR in the current repo. - A stored number without a URL is checked only when
gh repo viewcan name the current repository. - When
ghis missing, not signed in, or can't answer, gc warns on stderr (Warning: pull request status unavailable for <name>) and skips the worktree withpr status unavailable. It never fails the run or removes the worktree on a guess.
Examples
ptm gc # show what would be removed
ptm gc --apply # remove merged, closed-PR, and orphaned worktrees
ptm gc --older-than 14d --apply # remove worktrees unused for 14 days
ptm gc --owner grok --force # remove one agent's stale worktrees, including dirty ones
ptm gc --merged --keep-branch --apply
ptm gc -p owner/repo --json--json
ptm gc --json prints one object listing each candidate, why it matched, and what happened to it:
{
"version": 1,
"applied": false,
"candidates": [
{
"name": "docs-typo",
"path": "/home/me/demo/.git/ptm/worktrees/docs-typo",
"branch": "docs-typo",
"owner": "claude",
"reasons": ["merged"],
"action": "skipped",
"detail": "dirty",
"branchDeleted": null
},
{
"name": "fix-login",
"path": "/home/me/demo/.git/ptm/worktrees/fix-login",
"branch": "fix-login",
"owner": "grok",
"reasons": ["merged"],
"action": "wouldRemove",
"detail": null,
"branchDeleted": null
}
],
"error": null
}appliedistruewhen removal was requested (--applyor--force).reasonscontains one or more ofmerged,pullRequest,orphaned,olderThan.actioniswouldRemove,removed,skipped, orfailed.detailexplains a skip or failure:current worktree,default branch,locked,other agent,dirty,unpushed,status unknown,pr status unavailable, a hook or Git error, or a note that the branch was kept.branchDeletedisnullfor a dry run or a skip.- If a removal fails, the top-level
errorobject is set and the exit code is 1. The other candidates are still listed. An empty plan exits 0. - An invalid
--older-thanvalue is avalidationerror (exit 3).
Worktree metadata: owner, purpose, and links (#813, closes #774)
When several agents work in the same repository, Git can tell you a worktree's branch and path, but not who created it, what it's for, or which PR it belongs to. ptm now records that when it creates a worktree and reports it everywhere a worktree is shown. This is also what lets ptm gc tell your worktrees apart from another agent's.
Recording it
ptm create and ptm attach accept four new options. ptm github checkout accepts --owner and --purpose and stores the PR or issue number and URL automatically.
| Option | Meaning |
|---|---|
--owner <id> |
Who created the worktree. When omitted, PTM_AGENT_ID is used if set. |
--purpose <text> |
Free-text description of the task. |
--pr <number|url> |
Link a pull request (a number or an http(s) URL). |
--issue <number|url> |
Link an issue (a number or an http(s) URL). |
The simplest setup for an agent is to export PTM_AGENT_ID once, so every worktree it creates is tagged with its owner:
$ ptm create fix-login --owner grok --purpose "fix the login redirect" --issue 42
Created worktree 'fix-login' at /home/me/demo/.git/ptm/worktrees/fix-login
$ PTM_AGENT_ID=claude ptm create docs-typo --purpose "README typo"
Created worktree 'docs-typo' at /home/me/demo/.git/ptm/worktrees/docs-typoUpdating it: ptm meta set
ptm meta set <name> changes the record of an existing worktree. You need at least one of --owner, --purpose, --pr, --issue. Omitted fields keep their current values, and PTM_AGENT_ID is not applied. A worktree with no record yet gets one. An unknown name exits 2. --json prints the updated worktree object.
$ ptm meta set fix-login --pr https://github.com/acme/demo/pull/43
Updated metadata for 'fix-login'Reading it
ptm list shows [owner] and the purpose, and ptm list --owner <id> filters by owner (unowned worktrees are omitted):
$ ptm list
main (.)
docs-typo (.git/ptm/worktrees/docs-typo) [claude] README typo
fix-l...