LibraLibra
LibraLibra
DocsBlogLibra
Getting Started
Architecture
Design
Commands
libra addlibra agentlibra alternateslibra applylibra archivelibra authlibra automationlibra bisectlibra blamelibra branchlibra bundlelibra cachelibra cat-filelibra check-attrlibra check-ignorelibra check-mailmaplibra checkoutlibra cherry-picklibra cleanlibra clonelibra cloudlibra code-controllibra codelibra commit-treelibra commitlibra completionslibra configlibra credentiallibra depslibra describelibra diff-fileslibra diff-indexlibra diff-treelibra difflibra dirtylibra fast-exportlibra fast-importlibra fetchlibra filelibra for-each-reflibra format-patchlibra fscklibra gclibra graphlibra greplibra hash-objectlibra hookslibra hydratelibra index-packlibra initlibra investigatelibra layerlibra lfslibra loglibra logfilelibra loginlibra logoutlibra ls-fileslibra ls-remotelibra ls-treelibra maintenancelibra medialibra merge-baselibra merge-filelibra mergelibra metadatalibra mvlibra noteslibra oplibra openlibra pack-objectslibra packagelibra prunelibra publishlibra pulllibra pushlibra read-treelibra rebaselibra refloglibra remotelibra repacklibra replacelibra rererelibra resetlibra restorelibra rev-listlibra rev-parselibra revertlibra reviewlibra revisionlibra rmlibra sandboxlibra servicelibra shortloglibra show-reflibra showlibra sparse-viewlibra stashlibra statslibra statuslibra switchlibra symbolic-reflibra taglibra update-indexlibra update-reflibra usagelibra verify-packlibra whoamilibra worktreelibra write-tree
API Reference
Policy
Commands

libra worktree

Command reference for `libra worktree`

Manage multiple working trees attached to this repository.

Alias: wt

Synopsis

libra worktree add <path>
libra worktree list
libra worktree lock <path> [--reason <text>]
libra worktree unlock <path>
libra worktree move <src> <dest>
libra worktree prune
libra worktree remove <path>
libra worktree umount <path> [--cleanup]
libra worktree repair

Description

libra worktree manages multiple working trees that share a single repository database and object store. This allows you to have several checkouts of the same repository simultaneously, which is useful for working on multiple branches at once, running builds while editing code, or testing changes in isolation.

Each linked worktree is a directory containing a .libra symlink pointing back to the shared storage directory. The main worktree is the original repository directory. All worktrees share the same SQLite database, object store, and configuration.

Worktree metadata is persisted in a worktrees.json file inside the .libra storage directory. Each entry tracks the filesystem path, whether it is the main worktree, its lock status, and an optional lock reason. The state file is written atomically via a temporary file rename to prevent corruption.

When a new worktree is added and HEAD points to a commit, the worktree is automatically populated with the committed content from HEAD (not staged index changes).

Options

Subcommand: add

Create a new linked worktree at the given filesystem path.

ArgumentDescription
<path>Filesystem path for the new worktree. Can be relative or absolute. The directory is created if it does not exist. Must not be inside .libra storage, must not already be registered, and must be empty if it exists.
# Create a new worktree for a feature branch
libra worktree add ../my-feature
libra --json worktree add ../my-feature

# Create using absolute path
libra worktree add /tmp/libra-test

Subcommand: list

List all registered worktrees and their state. --porcelain emits a stable, machine-readable format: for each worktree a worktree <path> line, the shared HEAD <sha> line (when the repository has any commit), and a locked [<reason>] line when locked, with a blank line between worktrees. Because Libra worktrees share one HEAD/index/refs, Git's per-worktree branch/detached lines are intentionally omitted (Libra has no per-worktree HEAD).

libra worktree list
libra worktree list --porcelain
libra --json worktree list
libra --machine worktree list

Structured output uses the worktree.list command envelope. Each entry reports kind, path, is_main, locked, lock_reason, and whether the path currently exists on disk.

Subcommand: lock

Mark a worktree as locked to prevent it from being pruned or removed.

Argument / FlagDescription
<path>Filesystem path of the worktree to lock.
--reasonOptional human-readable explanation for why the worktree is locked.
# Lock a worktree
libra worktree lock ../my-feature

# Lock with a reason
libra worktree lock ../my-feature --reason "long-running experiment"
libra --json worktree lock ../my-feature --reason "long-running experiment"

Subcommand: unlock

Remove the lock from a previously locked worktree. Idempotent: unlocking an already-unlocked worktree is a no-op.

ArgumentDescription
<path>Filesystem path of the worktree to unlock.
libra worktree unlock ../my-feature
libra --machine worktree unlock ../my-feature

Subcommand: move

Move or rename an existing linked worktree. The directory is renamed on disk and the registry is updated. Cannot move the main worktree or a locked worktree.

ArgumentDescription
<src>Current filesystem path of the worktree.
<dest>New filesystem path. Must not already exist on disk or in the registry. Cannot be inside .libra storage.
libra worktree move ../my-feature ../my-feature-v2
libra --json worktree move ../my-feature ../my-feature-v2

Subcommand: prune

Remove worktrees from the registry whose directories no longer exist on disk. The main worktree and locked worktrees are never pruned.

libra worktree prune
libra --machine worktree prune

Subcommand: remove

Unregister a worktree from the state file. By default the directory on disk is intentionally left untouched to avoid destructive behavior. Pass --delete-dir for Git-style behavior — the directory is removed only after a dirty-state check passes. Cannot remove the main worktree or a locked worktree.

Argument / FlagDescription
<path>Filesystem path of the worktree to unregister.
--delete-dirAfter unregistering, also delete the directory on disk. Refused when the worktree contains uncommitted changes (staged or unstaged).
# Default — keep the directory on disk
libra worktree remove ../my-feature
libra --json worktree remove ../my-feature

# Git-style — also delete the directory (clean worktree only)
libra worktree remove --delete-dir ../my-feature
libra --machine worktree remove --delete-dir ../my-feature

# Refused when dirty:
$ libra worktree remove --delete-dir ../dirty-feature
fatal: cannot delete dirty worktree '../dirty-feature' (uncommitted changes)
       Hint: commit or stash changes, or remove without --delete-dir to keep the directory

Behavior intentionally differs from Git: Git's default deletes the directory. Libra keeps it by default to prevent accidental data loss; --delete-dir restores Git-like semantics opt-in. See COMPATIBILITY.md and compatibility/worktree-surface.md for the rationale.

Subcommand: umount

Unmount a FUSE worktree mountpoint. This is primarily useful for cleaning up stale Agent task worktrees when the operating system reports a path as busy. The command also accepts a Libra task worktree root and resolves its workspace mountpoint automatically.

Alias: unmount

Argument / FlagDescription
<path>FUSE mountpoint path, or a Libra task worktree root containing a workspace mountpoint.
--cleanupAfter unmounting, remove the Libra task worktree root. Only task FUSE worktree paths are accepted.
libra worktree umount /repo/.libra/worktrees/tasks/libra-task-worktree-fuse-29353-id/workspace --cleanup
libra --json worktree umount /repo/.libra/worktrees/tasks/libra-task-worktree-fuse-29353-id --cleanup

JSON / machine output envelope:

{
  "ok": true,
  "command": "worktree.umount",
  "data": {
    "mountpoint": "/repo/.libra/worktrees/tasks/libra-task-worktree-fuse-29353-id/workspace",
    "unmounted": true,
    "cleanup_requested": true,
    "cleanup_root": "/repo/.libra/worktrees/tasks/libra-task-worktree-fuse-29353-id",
    "cleanup_root_removed": true
  }
}

Subcommand: repair

Repair worktree metadata by removing duplicate entries (same canonical path) and ensuring exactly one main worktree entry exists. Only writes the state file if changes are actually made.

libra worktree repair
libra --json worktree repair

Common Commands

# Create a new worktree
libra worktree add ../experiment

# List all worktrees
libra wt list

# Lock a worktree to protect it
libra wt lock ../experiment --reason "production hotfix in progress"

# Unlock when done
libra wt unlock ../experiment

# Move a worktree to a new location
libra wt move ../experiment ../experiment-v2

# Clean up worktrees whose directories were deleted
libra wt prune

# Unregister a worktree (keeps files on disk)
libra wt remove ../experiment-v2

# Fix inconsistent worktree metadata
libra wt repair

Human Output

worktree add:

/Users/alice/projects/my-feature

worktree list:

main /Users/alice/projects/my-repo
worktree /Users/alice/projects/my-feature
worktree /Users/alice/projects/hotfix [locked: production hotfix in progress]

worktree remove:

Removed worktree '/Users/alice/projects/my-feature' from registry. Directory kept on disk.
Removed worktree '/Users/alice/projects/my-feature' from registry and deleted directory.

worktree prune (with stale entries):

Will prune 2 worktrees:
  /Users/alice/projects/old-experiment
  /Users/alice/projects/deleted-branch
Pruned 2 worktrees

worktree prune (nothing to prune):

No worktrees to prune

JSON Output

worktree add, lock, unlock, move, prune, remove, and repair use command-specific envelopes. --machine emits the same schemas as compact single-line JSON.

worktree.add:

{
  "ok": true,
  "command": "worktree.add",
  "data": {
    "path": "/Users/alice/projects/my-feature",
    "already_exists": false
  }
}

worktree.list:

{
  "ok": true,
  "command": "worktree.list",
  "data": {
    "worktrees": [
      {
        "kind": "main",
        "path": "/Users/alice/projects/my-repo",
        "is_main": true,
        "locked": false,
        "lock_reason": null,
        "exists": true
      }
    ]
  }
}

worktree.lock:

{
  "ok": true,
  "command": "worktree.lock",
  "data": {
    "path": "/Users/alice/projects/my-feature",
    "locked": true,
    "lock_reason": "long-running experiment",
    "changed": true
  }
}

worktree.unlock:

{
  "ok": true,
  "command": "worktree.unlock",
  "data": {
    "path": "/Users/alice/projects/my-feature",
    "locked": false,
    "changed": true
  }
}

worktree.move:

{
  "ok": true,
  "command": "worktree.move",
  "data": {
    "source": "/Users/alice/projects/my-feature",
    "destination": "/Users/alice/projects/my-feature-v2",
    "registry_updated": true,
    "disk_directory_moved": true
  }
}

worktree.prune:

{
  "ok": true,
  "command": "worktree.prune",
  "data": {
    "pruned": ["/Users/alice/projects/old-experiment"],
    "pruned_count": 1
  }
}

worktree.remove:

{
  "ok": true,
  "command": "worktree.remove",
  "data": {
    "path": "/Users/alice/projects/my-feature",
    "registry_removed": true,
    "disk_directory_deleted": false
  }
}

worktree.repair:

{
  "ok": true,
  "command": "worktree.repair",
  "data": {
    "changed": true
  }
}

Design Rationale

Why JSON-file persistence instead of filesystem links like Git?

Git tracks worktrees through a combination of filesystem structure: the main .git/worktrees/ directory contains per-worktree directories with gitdir, HEAD, and commondir files, and each linked worktree has a .git file (not directory) pointing back. This approach is tightly coupled to Git's file-based architecture and requires careful cross-referencing between multiple locations.

Libra uses a single worktrees.json file in the shared storage directory. This provides several advantages: all worktree metadata is in one queryable location, state is written atomically (via temp-file rename), and the format is trivially inspectable by both humans and AI agents. The symlink from each linked worktree's .libra back to the shared storage is simpler than Git's bidirectional pointer system. The trade-off is that the JSON file is a single point of truth that must be kept consistent, which is why repair exists.

Why --reason on lock?

Git's git worktree lock also supports --reason, and Libra preserves this. Lock reasons are valuable in team environments and when AI agents manage worktrees: they provide context about why a worktree should not be pruned or removed. Without a reason, a locked worktree is opaque, and another user (or agent) cannot determine whether the lock is still relevant. The reason is displayed in list output, making lock status self-documenting.

Why does remove not delete directories on disk?

Deleting files is a destructive operation that cannot be undone. Libra's remove only unregisters the worktree from the JSON state file, leaving the directory intact. This is a deliberate safety choice: the user can inspect and manually delete the directory when they are confident it is no longer needed. This also prevents accidental data loss if a worktree contains uncommitted work. Git's git worktree remove does delete the directory by default, which has been a source of lost work.

Why does move reject locked worktrees?

A locked worktree signals that it should not be modified. Moving it would change its filesystem path, which could break references to that path in other tools, scripts, or agent configurations. The user must explicitly unlock the worktree before moving it, ensuring the action is intentional.

Why does add populate from HEAD instead of the index?

When creating a linked worktree, Libra restores content from the HEAD commit rather than the current index state. This ensures the new worktree reflects the last committed state, not any staged-but-uncommitted changes that exist only in the original worktree's context. This matches user expectations: a new worktree starts from a known good state.

Parameter Comparison: Libra vs Git vs jj

OperationLibraGitjj
Create worktreeworktree add <path>worktree add <path> [<branch>]workspace add <path>
Create on branchNot supportedworktree add <path> <branch>workspace add <path> (then jj edit)
Create detachedNot supportedworktree add --detach <path> <commit>N/A
List worktreesworktree listworktree list [--porcelain]workspace list
Lockworktree lock <path> [--reason]worktree lock [--reason] <worktree>N/A
Unlockworktree unlock <path>worktree unlock <worktree>N/A
Moveworktree move <src> <dest>worktree move <worktree> <new-path>N/A
Pruneworktree pruneworktree prune [--dry-run]N/A (automatic)
Removeworktree remove <path> (registry only)worktree remove [--force] <worktree> (deletes dir)workspace forget <name>
Repairworktree repairworktree repair [<path>...]N/A
AliaswtN/AN/A
Branch per worktreeNot supportedAutomatic (new branch or existing)Automatic (new working copy commit)
StorageJSON file (worktrees.json)Filesystem structure (.git/worktrees/)Operation log
Worktree linkSymlink to shared .libra.git file pointing to gitdirSymlink to shared .jj

Note: jj uses the term "workspace" instead of "worktree". Each workspace automatically gets its own working copy commit, and workspaces are tracked in the operation log. jj workspaces are simpler than Git worktrees because jj's change-based model does not require separate branch management per workspace.

Error Handling

CodeCondition
LBR-REPO-001Not a libra repository
LBR-REPO-002worktrees.json is corrupt
LBR-CLI-003Worktree path cannot be inside .libra storage
LBR-CLI-003Target exists and is not a directory
LBR-CLI-003No such worktree (for lock, unlock, move, remove)
LBR-CLI-003Cannot move or remove main worktree
LBR-CLI-003Cannot move or remove locked worktree
LBR-CLI-003worktree umount --cleanup was requested for a non-task FUSE worktree path
LBR-CONFLICT-002Target directory exists and is not empty
LBR-CONFLICT-002Target already contains a .libra entry
LBR-CONFLICT-002Destination already exists (for move)
LBR-CONFLICT-002Destination already registered as worktree (for move)
LBR-CONFLICT-002--delete-dir refused because the worktree is dirty
LBR-IO-001Failed to read or inspect worktree paths/state/status
LBR-IO-002Failed to write worktrees.json
LBR-IO-002Failed to populate worktree from HEAD

libra whoami

Command reference for `libra whoami`

libra write-tree

Command reference for `libra write-tree`

On this page

SynopsisDescriptionOptionsSubcommand: addSubcommand: listSubcommand: lockSubcommand: unlockSubcommand: moveSubcommand: pruneSubcommand: removeSubcommand: umountSubcommand: repairCommon CommandsHuman OutputJSON OutputDesign RationaleWhy JSON-file persistence instead of filesystem links like Git?Why --reason on lock?Why does remove not delete directories on disk?Why does move reject locked worktrees?Why does add populate from HEAD instead of the index?Parameter Comparison: Libra vs Git vs jjError Handling