git-forest manages one named workspace across multiple Git repositories using
linked worktrees. Git exposes the installed binary as both:
git-forest <command>
git forest <command>Running git forest without a subcommand opens an interactive workspace
launcher. The existing management commands remain non-interactive; open
explicitly starts the same launcher. Forest contacts remotes only for explicit
setup, fetch, and update commands. It does not reset or delete branches,
start runtime services, or maintain a separate worktree registry. Git worktree
metadata and the filesystem are authoritative. The launcher and the explicit
attach command can create or focus a workspace in a running
Herdr session.
The project currently tests Linux and macOS. Building from source requires Git
and Rust; the repository pins its Rust toolchain in
rust-toolchain.toml.
Clone the repository and use the just install
recipe:
git clone https://github.com/hiradp/git-forest.git
cd git-forest
just installThis installs the executable into Cargo's binary directory and the
git-forest(1) manual into the same prefix. The manual is required because Git
interprets git forest --help as a request for a manual page before it invokes
an external Git command. The executable directory (normally $CARGO_HOME/bin)
must be on PATH so both the command and its manual can be discovered.
Set CARGO_INSTALL_ROOT to choose another installation prefix:
CARGO_INSTALL_ROOT="$HOME/.local" just installCargo itself installs only executables. If you instead run
cargo install --locked --path ., use git forest -h or git-forest --help
for command-line help; git forest --help requires the manual installed by the
just recipe.
Forest generates shell setup that calls back into the executable for dynamic completion. Add the command for your shell to its startup file:
For Bash, add this to ~/.bashrc:
source <(git-forest completions bash)For Zsh, add this to ~/.zshrc:
source <(git-forest completions zsh)For Fish, add this to ~/.config/fish/config.fish:
git-forest completions fish | sourceFor Elvish, use eval (git-forest completions elvish | slurp). For
PowerShell, use
git-forest completions powershell | Out-String | Invoke-Expression.
Keep the generator invocation in the startup file rather than saving its
output because the completion protocol is version-dependent.
Bash, Zsh, and Fish setup completes both git-forest and git forest.
Completion includes commands and options, configured repository names, existing
workspace names for commands such as attach, and existing primary and named
checkouts for remove. It honors normal configuration discovery,
FOREST_CONFIG, and --config. Completion is read-only: it does not contact
remotes, mutate worktrees, or invoke Herdr. Invalid or missing configuration
simply produces no configuration-dependent candidates.
git-forest reads a .forest.toml:
version = 1
[repositories]
root = "src"
remote = "git@github.com:example/{name}.git"
members = [
"api",
"operator",
]
[workspaces]
root = "src/.workspaces"
branch = "user/{checkout}"All paths are relative to the directory containing .forest.toml.
repositories.root contains the canonical clones. Each member is both its CLI
name and its directory beneath that root. repositories.remote is optional for
projects that provision repositories separately. It is required by setup
when a canonical clone is missing. Relative local remotes are resolved from the
directory containing .forest.toml.
The only supported placeholders are:
{name}inrepositories.remote;{workspace}and{checkout}inworkspaces.branch.
{workspace} is always the workspace name. {checkout} is the explicit slot
for a named checkout and the workspace name for a primary checkout. Thus the
template user/{checkout} renders user/stacked for api in workspace
stacked and user/part-2 for api@part-2. A branch template must contain at
least one supported placeholder. When present, the remote template must contain
{name}. Unknown placeholders, duplicate members, absolute roots, and
unsupported configuration versions are rejected.
Configuration precedence is:
--config <path>;FOREST_CONFIG;.forest.tomlfound by walking from the current directory to the filesystem root.
Workspace names and checkout slots must match
[A-Za-z0-9][A-Za-z0-9._-]*. . and .. are not valid names. Forest reserves
.archive directly beneath workspaces.root for archived workspaces. A checkout is
selected as repository for its primary worktree or repository@slot for an
additional worktree from the same canonical repository. A single request may
not contain checkout identifiers that differ only by ASCII case because they
alias on case-insensitive filesystems.
git forest
git forest open
git forest setup [--json]
git forest repos [--json]
git forest fetch [<repository>...] [--jobs <N>] [--json]
git forest update [<repository>...] [--jobs <N>] [--json]
git forest create <workspace> [<checkout>...] [--symbol <symbol>] [--parent <workspace>] [--base <checkout>=<ref>]... [--branch <checkout>=<branch>]... [--json]
git forest add <workspace> <checkout>... [--base <checkout>=<ref>]... [--branch <checkout>=<branch>]... [--json]
git forest list [--archived] [--json]
git forest status [<workspace>] [--json]
git forest path <workspace> [--json]
git forest attach <workspace> [--json]
git forest rename <workspace> <new-workspace> [--json]
git forest archive <workspace> [--force] [--json]
git forest unarchive <workspace> [<checkout>...] [--archive <ID>] [--as <workspace>] [--symbol <symbol>] [--base <checkout>=<ref>]... [--branch <checkout>=<branch>]... [--json]
git forest delete <workspace> [--force] [--json]
git forest clean [--json]
git forest remove <workspace> [<checkout>...] [--force] [--json]
git forest completions <shell>
Global options:
--config <path>
-h, --help
-V, --version
Run git forest in a terminal to open the workspace launcher. git forest open
is the explicit equivalent. Start typing to fuzzy-search workspaces, then press
enter to attach the selected workspace in Herdr.
π² Forest
Pick a workspace. Weβll get it ready.
β Where do you want to work?
βΊ + Create a new workspace
β logical-slots api Β· operator
β review-123 api
[search Β· ββ move Β· space select Β· enter open Β· ctrl+d/del actions Β· esc leave]
The first picker also offers Create a new workspace. Forest prompts for a
valid name and presents the configured repositories as a searchable
multi-select. After a successful preflight it creates the linked worktrees and
attaches the new workspace. The launcher creates one primary checkout per
selected repository using the configured branch template and each repository's
local origin/HEAD; use the
non-interactive create and add commands for named checkouts or branch and
base overrides.
Press Space to select any number of existing workspaces, then Ctrl+D or Delete
to choose Archive, Force archive, Delete, or Force delete for the selection. If
nothing is selected, the action applies only to the highlighted workspace. A
single confirmation covers the full selection. Archive moves workspace-local
files beneath .archive; Delete permanently removes them.
Non-force actions refuse dirty worktrees, while force actions explicitly discard
dirty worktree changes. All four actions preserve Git branches. Press escape at
any prompt to go back or leave without making changes. The launcher honors
NO_COLOR. Without an interactive terminal, invoking Forest without a
subcommand prints help instead of waiting for input.
Ensures every configured canonical repository exists. Existing Git worktrees
are reused without fetching or changing their remotes. Missing repositories are
cloned from the rendered repositories.remote template in configuration order.
Before cloning, Forest checks every configured destination. An existing path that is not a Git worktree or a missing repository without a configured remote prevents all cloning. If a clone fails after earlier repositories succeeded, the successful clones are preserved and later repositories are not run. Repeating the command safely reuses completed clones and resumes the rest. Clones are completed in adjacent staging directories before being moved into the configured canonical paths, so an interrupted clone is never reused as a completed repository. Forest never overwrites an existing path.
Lists configured repositories in configuration order. Missing canonical clones are reported rather than making the whole command fail. For present clones it reports the origin URL and the default ref when available.
The default base is discovered exclusively through the symbolic ref
refs/remotes/origin/HEAD. The command never guesses main or master and
never contacts a remote to repair a missing default.
Fetches origin for every configured canonical repository. Pass repository
names to fetch only a subset. Fetches run concurrently, with at most 16 in
flight by default; use --jobs <N> to change that bound. The report remains in
configuration order. The command attempts every selected repository and exits
unsuccessfully if any fetch fails. Forest does not fetch tags because its
workspace operations consume remote-tracking branch refs only.
fetch updates remote-tracking refs, including the origin/HEAD target used as
the default creation base, but does not merge, reset, or otherwise update local
branches or worktrees. To create a workspace from the latest fetched defaults:
git forest fetch
git forest create logical-slots api operatorFetches the remote default branch, then fast-forwards its local branch in every
configured canonical repository. Pass repository names to update only a subset.
Fetches use the same bounded concurrency and --jobs <N> option as fetch, but
transfer only the branch identified by refs/remotes/origin/HEAD rather than
negotiating every remote branch and tag.
Forest does not hard-code main or master. If the branch is checked out, Forest updates its
worktree with a fast-forward-only merge. If it is not checked out, Forest moves
the local ref only after verifying a fast-forward. Dirty checked-out branches,
ignored paths that overlap incoming changes, missing local default branches,
locally ahead branches, and diverged branches are reported as conflicts and are
never reset or forced. A fetch or Git failure
in one repository does not prevent the other selected repositories from being
processed, but conflicts and failures make the command exit unsuccessfully.
git forest updatecreate permits the workspace directory to be absent. When no checkouts are
provided, it creates an empty workspace directory; repositories can be added
later with add. Repeating empty creation is safe. add requires an existing
workspace and at least one checkout. Both use the same idempotent creation
engine. Archived names remain available: create starts a fresh active workspace
without restoring or changing saved files. Existing branches may still be reused;
use unarchive to restore saved workspace files instead.
git forest create scratch
git forest add scratch apiAssign an emoji or symbol for the Herdr workspace name with --symbol:
git forest create logical-slots api operator --symbol "π²"
git forest attach logical-slots
# Herdr workspace name: π² logical-slotsRecord a parent workspace with --parent to group related workspaces, such as
a coordination workspace and the workspaces that implement its parts:
git forest create q4-storage --symbol "πΊ"
git forest create logical-slots api --parent q4-storage
git forest create slot-tests api@tests --parent logical-slotsThe parent must be an active workspace, and Forest rejects a parent that would form a cycle. Links can be any depth. Directories stay side by side under the workspaces root; the link is metadata only.
Symbols and parents are stored in the optional workspace-local
.forest-workspace.toml file:
symbol = "π²"
parent = "q4-storage"Both fields are optional. The file is authoritative and can be edited by hand;
Forest reads it on every command. Directory names, branch names, and JSON
workspace identifiers stay unchanged. Symbols may include composed emoji, but
must not contain whitespace or control characters. To set or change a symbol or
parent on an existing workspace, repeat create with --symbol or --parent;
omitting an option preserves the saved value. When Forest writes the file, it
keeps other keys but not comments. Metadata survives rename, archive, and
unarchive. unarchive --symbol and unarchive --parent override the saved
values. An invalid file is reported with its path. The file appears in
workspace entries like other workspace-local files.
Workspaces created by earlier versions may have a .forest-symbol file instead.
Forest still reads it, ignoring trailing whitespace, and replaces it with
.forest-workspace.toml the next time it writes metadata for that workspace.
Before mutation, every requested checkout is checked for:
- a present canonical Git worktree;
- a valid rendered branch name;
- a non-conflicting destination path;
- existing worktree registration;
- branches checked out elsewhere;
- branch namespace conflicts;
- a resolvable base when a new branch is needed.
An existing worktree is reused only when its path, canonical repository, and
branch all match. An existing branch that is not checked out elsewhere is added
without being recreated. New branches use origin/HEAD unless --base is
provided.
A primary checkout uses the repository name and retains the existing layout,
such as stacked/api. A named checkout uses repository@slot, such as
stacked/api@part-2. Its default branch is rendered with the slot as
{checkout}, allowing multiple branches from one repository in a workspace:
git forest create stacked api operator operator@part-2With branch = "user/{checkout}", this creates user/stacked at api and
operator, plus user/part-2 at operator@part-2. Checkout identifiers must
be unique within a request. Two checkouts from the same repository may not
select the same branch because Git permits a branch to be checked out only
once.
Use --branch <checkout>=<branch> to select a branch independently of the
configured template. Forest first uses an existing local branch. If it is
absent, Forest creates a local branch from refs/remotes/origin/<branch> and
configures the remote branch as its upstream. The remote-tracking ref must
already exist locally; creation never fetches. For example, to review a branch
after fetching:
git forest fetch api
git forest create review-123 api@fix --branch api@fix=contributor/fixCheckouts without a branch override continue to use the configured branch template. A branch override and a base override cannot both target the same checkout. After later fetches, Forest reports whether a tracking branch is behind but never merges, resets, or otherwise updates it implicitly.
Preflight conflicts prevent all mutation. If Git fails after earlier checkouts have been created, successful worktrees are preserved and later checkouts are marked as not run. Repeating the command resumes safely.
Human output keeps shared workspace details in one header and summarizes each checkout on a compact result line:
Workspace logical-slots
Path /project/src/.workspaces/logical-slots
Branch user/logical-slots
β api created new branch
β operator reused
Colors are enabled only when stdout is a terminal and can be disabled with
NO_COLOR. Use --json when every report field is needed.
list reconciles workspace directories with every canonical repository's Git
worktree metadata. Human output is a tree of workspace names, each under its
parent and prefixed with its saved symbol, or π² when it has none:
π¦ q4-storage
βββ π¦ logical-slots
βββ π² replica-lag
π² scratch
JSON keeps a flat list and reports each workspace's saved parent and
symbol, or null. It also reports primary and named checkouts separately,
along with unregistered paths, missing registered paths, workspace-local
entries, and layout mismatches; status shows these in human output.
Workspace-local entries are direct children that are not configured checkout
paths; they are supported and do not make the workspace inconsistent. The JSON
field remains unexpected_entries for compatibility.
list --archived shows saved archives instead of active workspaces, with each
archive's workspace, human-readable date, ID, and path.
Legacy archives show legacy instead of a date. Use the ID with
unarchive --archive <ID> when a workspace has several archives.
status additionally reports:
- current branch or detached state;
- HEAD commit;
- tracked and untracked dirty state;
- canonical worktree registration;
- upstream;
- ahead and behind counts.
Human output is exactly the absolute workspace path followed by a newline:
workspace=$(git forest path logical-slots)
tmux-sessionizer "$workspace" logical-slotsThe workspace must exist.
Opens an existing Forest workspace in Herdr. The herdr executable must be on
PATH, and a Herdr server for the current session must already be running.
Forest never starts or stops the server.
A workspace opens as a single tab with one shell pane rooted in the workspace
directory. Forest does not start commands. Where the tab goes depends on where
attach runs:
- If the workspace is already open anywhere, as its own Herdr workspace or as a tab, Forest focuses it and never opens a second copy.
- Otherwise, if the Herdr workspace that
attachruns in has the workspace's parent open, either as that Herdr workspace or as one of its tabs, Forest adds a tab there. This applies from any tab in that Herdr workspace. A Herdr workspace that lost its Forest tokens still counts as the parent when its label names the parent and one of its panes is rooted in the parent's directory; Forest tags it again. - Otherwise, Forest creates a Herdr workspace whose only tab is
main.
Forest identifies the Herdr workspace that attach runs in from
HERDR_WORKSPACE_ID, which Herdr sets in its panes. Outside Herdr, the third
rule always applies.
Herdr: πΊ q4-storage
main
π² logical-slots
slot-tests
A child tab is labeled with the workspace's symbol if it has one and its name,
such as π² logical-slots. A workspace opened as its own Herdr
workspace is named with its symbol and a space before its name. Without a
symbol, new Herdr workspaces use the plain workspace name and existing Herdr
names are left alone. Each attachment renames a managed tab whose label has
drifted.
If a saved parent is not an active workspace, Forest prints a warning and
ignores it; JSON reports carry the warning in warnings. Parents that form a
cycle are rejected before Forest contacts Herdr.
Attaching a workspace also opens all of its descendants. The workspace attaches by the rules above. Each descendant then opens as a tab in that same Herdr workspace, depth-first in name order. A descendant that is already open stays where it is, even in another Herdr workspace, because Herdr cannot move tabs between workspaces. Only the named workspace is focused. Forest checks every workspace in the tree before contacting Herdr, so a descendant with invalid metadata or inconsistent worktrees blocks attaching its ancestors until it is fixed. If Herdr fails partway, rerunning the command opens only what is missing.
Forest records a fixed-length identifier derived from the canonical workspace path in Herdr's runtime metadata: on the Herdr workspace for a workspace opened on its own, and on the tab's pane for a child tab. Herdr truncates metadata values to 80 bytes, so Forest does not store the path itself. Herdr workspaces tagged with the full path by earlier versions are still recognized when the path fit, and Forest adds the new identifier the first time it sees one. Tabs and panes Forest did not create keep their positions. Herdr workspaces created by earlier versions with one tab per checkout are reused; their checkout tabs are left open and are no longer managed. Multiple matches are rejected rather than guessed.
Attachment does not create worktrees or otherwise change Git state. A workspace with inconsistent configured worktrees or invalid metadata is rejected. Removing a Forest workspace does not close its Herdr workspace, tabs, or processes.
Human output summarizes the attached tab:
Workspace logical-slots
Path /project/src/.workspaces/logical-slots
Parent q4-storage
Herdr w1
β 2-π² logical-slots created /project/src/.workspaces/logical-slots
Renames an active workspace without changing its branches, commits, upstreams, or working tree contents:
git forest rename logical-slots review-123Forest preflights the complete workspace, atomically moves its directory, and
uses git worktree repair to update each canonical repository's worktree
metadata. Dirty, untracked, and ignored files are preserved, as are
workspace-local entries. An inconsistent source or an active destination
prevents all mutation. Archives do not reserve names and remain untouched.
Rename never fetches and has no --force option.
Existing branch names are authoritative and remain unchanged. If the branch
template uses {workspace}, a checkout created after the rename uses the new
workspace name while existing checkouts retain branches rendered with the old
name. Forest cannot safely infer which branches came from the template rather
than an explicit --branch override.
A Git repair failure can leave the directory at the new path with one or more
registrations still pointing to the old path. Repeating the same rename command
recognizes that partial state and resumes repair. Forest does not update Herdr
runtime metadata, so a later attach under the new name may create a new Herdr
workspace rather than reuse one attached before the rename.
Before moving anything, Forest updates parent in every active workspace that
names the old workspace as its parent, and restores those links if the
directory move fails. A resumed rename updates any children that still name the
old workspace.
Archives an entire workspace while preserving workspace-local files and
directories. Forest first preflights every configured checkout, then removes all
clean registered worktrees with git worktree remove. Branches are preserved.
After every removal succeeds, Forest atomically moves the remaining workspace
directory to a new generation beneath <workspaces.root>/.archive/.generations
without replacing an existing destination.
git forest archive nkdb-azure
# Saved beneath .archive/.generations/nkdb-azure--2026-09-26-09-46-11-0700Generation names use <workspace>--<archive_id>. IDs use local time and its UTC
offset (YYYY-MM-DD-HH-MM-SSΒ±HHMM), adding -2, -3, etc. for same-second
collisions. Older generations remain untouched. Legacy .archive/<workspace>
directories use ID legacy; .generations keeps timestamp-looking legacy names
unambiguous without a manifest or database. Within each workspace, archives sort
by actual instant and numeric suffix, with legacy first.
Dirty worktrees, unregistered checkout paths, layout mismatches, a missing
workspace directory, or active child workspaces prevent archival. Archive or
delete the children first, or point them at another parent. When the interactive
launcher retires several selected workspaces, it retires children before their
parents. Passing --force removes dirty worktrees
anyway, discarding their modified, untracked, and ignored files; all other
conflicts still prevent archival. Workspace-local entries do not. A Git failure
can leave a partially removed active workspace, and repeating the command safely
resumes. Forest serializes its create, add, rename, archive, unarchive,
delete, clean, and remove mutations for a configuration so they cannot race
one another. Repeating a successful archive reports already_archived and the
newest generation (or the legacy archive if it is the only one) while no new
active workspace exists. Archives are excluded from ordinary list, the
interactive launcher, active workspace completion, and path; use
list --archived to see them. Forest does not close matching Herdr processes.
The archive is local dormant storage, not a compressed archive or backup.
Restores saved workspace-local files, optionally creating only the checkouts you
request. Previous checkouts are not recreated automatically: Forest keeps no
manifest of them. Branch and base overrides work as for create.
git forest list --archived
git forest unarchive nkdb-azure api --archive 2026-09-26-09-46-11-0700 --as review-123A single archive is selected automatically; multiple archives require
--archive <ID> (including legacy for an older archive). --as <workspace>
chooses a different active name. Forest preflights every requested checkout
before moving saved files, refuses an existing active destination, and never
overwrites files. Restoration consumes the selected archive, leaving other
generations untouched.
If checkout creation fails after the move, repeat the command. When no matching
archive remains and the destination is active, Forest reconciles the requested
checkouts and reports already_active. It never merges an archive into an active
workspace. Without a manifest, it cannot verify the workspace's origin or
distinguish a well-formed nonexistent ID from a consumed one. Malformed IDs are
rejected before reconciliation. Invalid IDs and ambiguous selections return
conflict reports.
Permanently deletes a workspace. Forest first applies the same complete-workspace
preflight and worktree removal as archive, preserving branches and refusing
dirty or unregistered worktrees and workspaces with active children. It stages
the remaining workspace-local files at .archive/.deleting/<workspace>, then
permanently removes them. Pending deletions
are excluded from archive discovery and cannot be restored with unarchive.
Pass --force to discard modified, untracked, or ignored files in registered
worktrees as well.
Force never deletes branches and does not bypass unregistered-path checks.
Deleting an active workspace preserves all older archives of that name.
If file removal fails after staging, delete reports failed; repeat the
command to remove the pending files. If a new active workspace has reused the
name, retry refuses both paths until you rename the active workspace.
With no active workspace, remaining registrations, or pending deletion, delete
reports already_deleted. It never falls back to retained archives. To delete
an archive, including a legacy archive, restore it with unarchive --archive <ID>
(optionally --as <workspace>), then delete the restored workspace.
Removes stale Git registrations left behind when Forest worktree directories have already been deleted outside Forest:
git forest cleanUse remove or archive for normal workspace retirement so Forest can reject
dirty worktrees; clean only reconciles registrations after files are already
missing and cannot recover deleted contents.
Forest scans every configured canonical repository and runs
git worktree remove for registered worktrees beneath the active workspace
root whose paths no longer exist. It preserves branches, ignores present
worktrees, and does not touch stale registrations outside the configured
workspace root or beneath .archive. The command attempts every stale
registration and exits unsuccessfully if any removal fails. Repeating it is
safe; when nothing needs cleaning, it succeeds with an empty report.
Removal is deliberately conservative:
- modified, untracked, and ignored files prevent removal unless
--forceis passed, in which case they are discarded viagit worktree remove --force; - every selected path must be registered with its configured canonical
repository, even with
--force; - removal always uses
git worktree remove, including to clean up registered worktrees whose paths are already missing; - branches are never deleted;
- the workspace directory is removed only when it is empty and no active workspace names it as a parent;
- workspace-local entries are reported and preserved.
A saved .forest-workspace.toml is preserved too, so remove leaves the
workspace directory in place even after its last checkout is removed. Use
delete to remove the workspace and its local files.
Removing only named checkout identifiers leaves other worktrees in place.
remove stacked api@part-2 removes only that named checkout; api continues
to mean only the primary checkout. Repeating a partially completed removal is
safe.
Paths are absolute. Optional values are represented as null rather than
omitted.
{
"repositories": [
{
"name": "api",
"path": "/project/src/api",
"remote": "git@github.com:example/api.git",
"status": "cloned",
"message": null
}
]
}Setup status is cloned, reused, conflict, failed, or not_run.
{
"repositories": [
{
"name": "api",
"path": "/project/src/api",
"exists": true,
"is_git_worktree": true,
"origin_url": "git@github.com:example/api.git",
"default_ref": "refs/remotes/origin/main"
}
]
}{
"repositories": [
{
"name": "api",
"path": "/project/src/api",
"status": "fetched",
"message": null
}
]
}Fetch status is fetched or failed.
{
"repositories": [
{
"name": "api",
"path": "/project/src/api",
"branch": "main",
"status": "updated",
"message": null
}
]
}Update status is updated, up_to_date, conflict, or failed. branch is
null when origin/HEAD or the fetch result does not identify a default branch.
{
"workspace": "logical-slots",
"path": "/project/src/.workspaces/logical-slots",
"repositories": [
{
"name": "api",
"checkout": "api",
"slot": null,
"path": "/project/src/.workspaces/logical-slots/api",
"branch": "user/logical-slots",
"base_ref": "refs/remotes/origin/main",
"action": "create_branch",
"status": "created",
"message": null
}
]
}name remains the configured repository name. checkout is its unique command
selector and slot is null for a primary checkout. action is reuse,
add_existing_branch, create_branch, or null for a conflict discovered
before an action could be selected. A branch created to track an explicit
--branch uses create_branch, with its remote-tracking ref in base_ref.
status is reused, created, conflict, failed, or not_run.
{
"workspaces": [
{
"name": "logical-slots",
"path": "/project/src/.workspaces/logical-slots",
"exists": true,
"parent": "q4-storage",
"symbol": "π¦",
"repositories": [
{
"name": "api",
"checkout": "api",
"slot": null,
"path": "/project/src/.workspaces/logical-slots/api",
"exists": true,
"registered": true,
"branch": "user/logical-slots",
"head": "0123456789abcdef",
"inconsistencies": []
}
],
"unexpected_entries": [],
"inconsistencies": []
}
]
}list --archived --json returns archives instead of active workspaces:
{
"archives": [
{
"workspace": "nkdb-azure",
"archive_id": "2026-09-26-09-46-11-0700",
"archive_path": "/project/src/.workspaces/.archive/.generations/nkdb-azure--2026-09-26-09-46-11-0700"
}
]
}{
"workspaces": [
{
"name": "logical-slots",
"path": "/project/src/.workspaces/logical-slots",
"exists": true,
"parent": "q4-storage",
"repositories": [
{
"name": "api",
"checkout": "api",
"slot": null,
"path": "/project/src/.workspaces/logical-slots/api",
"exists": true,
"registered": true,
"branch": "user/logical-slots",
"detached": false,
"head": "0123456789abcdef",
"dirty": false,
"upstream": "origin/user/logical-slots",
"ahead": 1,
"behind": 0,
"inconsistencies": []
}
],
"unexpected_entries": [],
"inconsistencies": []
}
]
}{
"workspace": "logical-slots",
"path": "/project/src/.workspaces/logical-slots"
}{
"workspace": "logical-slots",
"path": "/project/src/.workspaces/logical-slots",
"parent": "q4-storage",
"herdr_workspace_id": "w1",
"status": "created",
"tabs": [
{
"label": "π² logical-slots",
"path": "/project/src/.workspaces/logical-slots",
"herdr_tab_id": "w1:t2",
"status": "created"
}
],
"warnings": [],
"descendants": []
}tabs always holds the single attached tab. parent is the saved parent, or
null. descendants lists a report of the same shape for each descendant in
attach order, and is empty for a workspace without children; each carries its
own herdr_workspace_id.
Workspace and tab status is one of created, reused, or reconciled.
{
"old_workspace": "logical-slots",
"old_path": "/project/src/.workspaces/logical-slots",
"workspace": "review-123",
"path": "/project/src/.workspaces/review-123",
"repositories": [
{
"name": "api",
"checkout": "api",
"slot": null,
"old_path": "/project/src/.workspaces/logical-slots/api",
"path": "/project/src/.workspaces/review-123/api",
"branch": "user/logical-slots",
"status": "repaired",
"message": null
}
],
"status": "renamed",
"message": null
}Workspace rename status is renamed, conflict, or failed. Repository
status is repaired, already_repaired, failed, or not_run.
{
"workspace": "logical-slots",
"path": "/project/src/.workspaces/logical-slots",
"archive_id": "2026-09-26-09-46-11-0700",
"archive_path": "/project/src/.workspaces/.archive/.generations/logical-slots--2026-09-26-09-46-11-0700",
"repositories": [
{
"name": "api",
"checkout": "api",
"slot": null,
"path": "/project/src/.workspaces/logical-slots/api",
"status": "removed",
"message": null
}
],
"status": "archived",
"preserved_entries": [
"/project/src/.workspaces/.archive/.generations/logical-slots--2026-09-26-09-46-11-0700/notes.md"
],
"message": null
}Archive status is archived, already_archived, conflict, or failed.
Repository removal statuses have the same meanings as for remove.
Restoring saved files without requesting checkouts:
{
"source_workspace": "nkdb-azure",
"workspace": "review-123",
"path": "/project/src/.workspaces/review-123",
"archive_id": "2026-09-26-09-46-11-0700",
"archive_path": "/project/src/.workspaces/.archive/.generations/nkdb-azure--2026-09-26-09-46-11-0700",
"repositories": [],
"status": "unarchived",
"message": null
}Unarchive status is unarchived, already_active, conflict, or failed.
Requested checkout reports in repositories use the same fields and statuses
as create. archive_path records the selected source, even after its move;
it is null on a retry when that archive has already been consumed.
archive_id is null when no archive is selected and no ID was supplied.
{
"workspace": "logical-slots",
"path": "/project/src/.workspaces/logical-slots",
"repositories": [
{
"name": "api",
"checkout": "api",
"slot": null,
"path": "/project/src/.workspaces/logical-slots/api",
"status": "removed",
"message": null
}
],
"status": "deleted",
"deleted_entries": [
"/project/src/.workspaces/.archive/.deleting/logical-slots/notes.md"
],
"message": null
}Delete status is deleted, already_deleted, conflict, or failed.
deleted_entries lists the deletion staging paths of removed workspace-local
entries, excluding retained archives. Repository removal statuses have the same
meanings as for remove.
{
"worktrees": [
{
"workspace": "logical-slots",
"name": "api",
"checkout": "api",
"slot": null,
"path": "/project/src/.workspaces/logical-slots/api",
"status": "removed",
"message": null
}
]
}Clean status is removed or failed.
{
"workspace": "logical-slots",
"path": "/project/src/.workspaces/logical-slots",
"repositories": [
{
"name": "api",
"checkout": "api",
"slot": null,
"path": "/project/src/.workspaces/logical-slots/api",
"status": "removed",
"message": null
}
],
"workspace_removed": true,
"remaining_entries": []
}Removal status is removed, already_absent, conflict, failed, or
not_run.
Application errors in JSON mode are emitted as JSON to stderr:
{
"error": {
"message": "invalid input: unknown repository \"unknown\"",
"exit_code": 2
}
}Operational conflict reports remain on stdout because they contain the result for every requested repository.
0: successful, including fully idempotent operations;1: an operational conflict or Git, Herdr, or filesystem failure;2: usage, input, or configuration error.
The test suite creates temporary repositories and local bare origins. It does not require network access or the developer's Git identity.
just check # apply formatting and Clippy fixes, then validate the tree
just test # run all testsjust check is intentionally allowed to update tracked files. CI runs the
non-mutating formatting and Clippy checks plus tests on fixed Linux and macOS
runner images. All Cargo commands in automation use the committed lockfile.
The security workflow audits Cargo.lock with RustSec, reviews dependency
changes on pull requests, and rejects workflow actions that are not pinned to a
full commit SHA. Dependabot proposes weekly Cargo and GitHub Actions updates.
See SECURITY.md for private vulnerability reporting.
Copyright (c) 2026 Hirad Pourtahmasbi. Licensed under the MIT License.