LAZYWORKTREE(1)

πŸ“¦
version 1.52.0 Β· 2 versions
, in nixpkgs unstable

✨ NAME ✨

Okay so real quick β€” say hi to lazyworktree (that's the LAZYWORKTREE(1) you're holding rn πŸ’…). It's a TUI tool to manage git worktrees. That's the whole one-liner, and honestly? It ate.


🌸 SYNOPSIS 🌸

Here's the shape of every way you can call her:

lazyworktree [OPTIONS]
lazyworktree wt-create [--from-branch BRANCH] [--name NAME] | [--from-pr
NUMBER] | [--from-pr-interactive|-P] | [--from-issue NUMBER] |
[--from-issue-interactive|-I] [--query|-q STRING] [--with-change]
[--silent]
lazyworktree wt-rename [--silent] [WORKTREE] [NEW-NAME]
lazyworktree wt-delete [--no-branch] [--silent]
lazyworktree cleanup [--all]

No cap, that's the whole menu of entry points 🫢


πŸ¦‹ DESCRIPTION πŸ¦‹

lazyworktree is a BubbleTea-based Terminal User Interface (TUI) built for efficient Git worktree management. Literally what it says on the tin, bestie. It lets you:

  • visualise the repository's status πŸ‘€
  • oversee branches 🌿
  • navigate between worktrees with ease ✨

It's giving "finally, my worktrees have a home screen."


πŸ’– FEATURES πŸ’–

Sit down, because the feature list is a whole mood board:

  • Worktree Management β€” Create, rename, delete, absorb, and prune merged worktrees. The full lifecycle, sorted πŸŽ€
  • Cherry-pick Commits β€” Copy commits from one worktree to another via an interactive worktree picker. Lowkey my fave πŸ’
  • Commit Log Details β€” The commit pane shows author initials alongside commit subjects. Cute and useful πŸ’«
  • Base Selection β€” Select a base branch or commit from a list, or enter a reference when creating a worktree. Pick your starting point, queen πŸ‘‘
  • Forge Integration β€” Fetch and display associated Pull Request (GitHub) or Merge Request (GitLab) status and CI checks with icons from the selected icon set when enabled, including optional author and reviewer avatar badges on Kitty-compatible terminals πŸ–ΌοΈ
  • Reviewers β€” Show how many people have submitted a review on the selected pull or merge request, who they are, and what each of them decided; see pr_reviewers under CONFIGURATION. Receipts, basically πŸ•΅οΈ
  • Create from PR/MR β€” Establish worktrees directly from open pull or merge requests via the create worktree menu (c) ⌨️
  • Create from current branch β€” Start a worktree from the branch you currently occupy; the branch name prompt offers a friendly random suggestion that you may override, and the checkbox shown during naming optionally carries over uncommitted work. Yes, it saves your messy uncommitted stuff 😭🫢
  • Checkout existing branch β€” When selecting an existing local branch, choose to associate a worktree with it directly (no new branch created) or create a new branch based on it 🌱
  • Create from Issue β€” Establish worktrees from GitHub/GitLab issues with automatic branch name generation πŸ€–
  • Status at a Glance β€” View dirty state, ahead/behind counts, and divergence from main. Vibe check, but for your repo πŸ’…
  • Tmux Integration β€” Create and manage tmux sessions per worktree with multi-window support πŸͺŸ
  • Zellij Integration β€” Create and manage zellij sessions per worktree with multi-tab support πŸ—‚οΈ
  • Herdr Integration β€” Create and manage Herdr workspaces or named sessions per worktree with multi-tab support πŸ‘
  • Container Execution β€” Run custom commands inside OCI containers (docker/podman) with automatic worktree mounting 🐳
  • Diff Viewer β€” View diff with optional delta support πŸ”
  • Repo Automation β€” .wt init/terminate commands with TOFU security 🍞
  • LazyGit Integration β€” Launch lazygit directly for the currently selected worktree πŸš€
  • Mouse Support β€” Click to focus panes, select items, and scroll with the mouse wheel πŸ–±οΈ
  • Metadata Menu β€” Press e in the worktree pane to edit the selected worktree's description, colour, notes, icon, and tags ✏️

⭐ OPTIONS ⭐

Every flag, bestie. No skipping, no summarising, we're doing this properly πŸ’œ

  • --worktree-dir DIR β€” Override the default worktree root directory. Default: ~/.local/share/worktrees $LWT_REPO_PATH is automatically set to the git repository root, enabling repo-local placement: $LWT_REPO_PATH/.worktrees. When the directory is inside the repository, the <repoName> segment is omitted so worktrees land directly under the configured directory. Add the directory to .gitignore so it does not appear in git status. πŸ™ˆ
  • --theme NAME β€” Select a UI theme. Available themes: dracula, dracula-light, narna, clean-light, catppuccin-latte, rose-pine-dawn, one-light, everforest-light, everforest-dark, solarized-dark, solarized-light, gruvbox-dark, gruvbox-light, nord, monokai, catppuccin-mocha, modern, tokyo-night, one-dark, rose-pine, ayu-mirage, kanagawa. If unspecified, lazyworktree attempts to auto-detect the theme based on the terminal's background colour (defaulting to rose-pine for dark or dracula-light for light). Aesthetic autopilot ✨
  • --show-syntax-themes β€” Display the default delta syntax-theme values for each UI theme. So you can see what your diffs will look like before committing to a look πŸ‘€
  • --search-auto-select β€” Start with filter focused and select first match on Enter. Efficiency icon πŸ’…
  • --output-selection FILE β€” Write the selected worktree path to FILE on exit (for shell integration). Script girlies rise 🐚
  • --debug-log PATH β€” Path to debug log file for troubleshooting. For when things are not slaying πŸ”§
  • --version β€” Print version information and exit. 🏷️
  • --completion SHELL β€” Generate shell completion script (bash, zsh, fish). Tab-tab-tab supremacy ⌨️
  • --config-file FILE β€” Path to configuration file, overriding the default XDG config directory location. Your config, your rules πŸ—‚οΈ
  • --config KEY=VALUE β€” Override config values from the command line (repeatable). Format: --config=lw.key=value Supported keys: theme, worktree_dir, sort_mode, auto_refresh, disable_pr, search_auto_select, fuzzy_finder_input, icon_set, avatar_badges, pr_reviewers, palette_mru, palette_mru_limit, git_pager, git_pager_args, git_pager_interactive, git_pager_command_mode, pager, editor, commit.auto_generate_command, max_untracked_diffs, max_diff_chars, refresh_interval_seconds, trust_mode, merge_method, branch_name_script, worktree_note_script, worktree_notes_path, worktree_note_type, issue_branch_name_template, pr_branch_name_template, session_prefix, use_herdr_for_workspace_create, herdr_workspace_create_layout, init_commands, terminate_commands. Examples: --config=lw.theme=nord, --config=lw.sort_mode=active This has the highest precedence and overrides all other configuration sources. πŸ‘‘ Like, all of them. Periodt.

🧚 SUBCOMMANDS 🧚

list

List all worktrees in tabular form without launching the TUI. Just the facts, no glitz πŸ“‹

Options:

  • --json β€” Output worktrees as a JSON array. Each entry includes path, name, branch, head, is_main, is_current, has_changes, linked_pr, pr_status, description, tags, note_present, note_updated_at, agent_sessions, agent_open, agent_activity, and agent_count fields. Mutually exclusive with --pristine. 🧾
  • --pristine β€” Show only worktrees with no uncommitted changes. Mutually exclusive with --json. Clean girl aesthetic 🧼
  • --main β€” Include the main worktree in the output (excluded by default). She's shy 🫣
  • --no-agent β€” Skip agent session loading. Omits agent_sessions, agent_open, agent_activity, and agent_count from JSON output. Use for faster scripting when agent state is not needed. Speedrun mode ⚑

create

Create a new worktree without launching the TUI. Headless, but make it fashion πŸ’…

Options:

  • NAME β€” Optional positional argument for the worktree and branch name. If not provided, the source branch is checked out as is in a worktree named after it (sanitised: lowercase, alphanumeric characters and hyphens only); when that branch is already checked out elsewhere, a new branch with a generated name is created from it instead. Can be used with or without --from-branch. 🏷️
  • --from-branch BRANCH β€” Create worktree from the specified branch. If not provided, uses the current branch from the working directory. If a name is provided as a positional argument, a branch of that name is created from the source branch. Otherwise the branch itself is checked out, unless it is already checked out in another worktree, in which case a new branch with a generated name is created. A remote branch such as origin/foo is checked out as a local foo branch, created to track the remote when it does not yet exist. 🌿
  • --from-pr NUMBER β€” Create worktree from PR number. Fetches the specified PR and generates the worktree name from pr_branch_name_template (with optional branch_name_script). The local branch name always matches the PR head branch name. πŸ”€
  • --from-issue NUMBER β€” Create worktree from issue number. Fetches the specified issue and creates a worktree with a sanitised branch name derived from the issue_branch_name_template (default: issue-{number}-{title}, e.g., issue-42-fix-memory-leak). Uses the current branch as the base; combine with --from-branch to specify a different base. If the branch already exists locally it is reused instead; when it is already checked out in a worktree, that worktree's path is returned. 🎟️
  • --from-issue-interactive, -I β€” Interactively select an open issue to create a worktree from. When fzf is installed, presents a searchable list with issue body preview; otherwise displays a numbered list for selection. Issues that appear to have a worktree already are marked [worktree] and shown in green; detection matches the issue_branch_name_template text before {number}, plus the issue number, against branch and worktree directory names, so renamed branches are not detected. Shares the same constraints as --from-issue (mutually exclusive with --from-pr, cannot combine with --with-change or a positional name). Combine with --from-branch to specify a base branch. 🟒
  • --from-pr-interactive, -P β€” Interactively select an open pull request to create a worktree from. When fzf is installed, presents a searchable list with PR metadata preview (author, branches, draft status, CI); otherwise displays a numbered list for selection. Pull requests whose branch is already checked out in a worktree are marked [worktree] and shown in green. Mutually exclusive with --from-pr, --from-issue, --from-issue-interactive, and --from-branch. Cannot combine with --with-change or a positional name. 🌈
  • --query STRING, -q STRING β€” Pre-filter the interactive selection list. Requires --from-pr-interactive or --from-issue-interactive. When fzf is available, the search box is pre-filled with the given string; otherwise the numbered list is filtered to items whose display line contains the string (case-insensitive substring match). Type-ahead slay πŸ”Ž
  • --generate β€” Generate the worktree name automatically from the current branch. Cannot be used with a positional name argument or --from-pr. Auto-magic πŸͺ„

…and that's genuinely not the end of the page, bestie β€” more is coming πŸ’œ

  • --with-change β€” carries your uncommitted changes over into the shiny new worktree 🫢 works with the current branch or with --from-branch. It stashes what's in your current worktree, creates the new worktree, then applies the stash. Nope, can't be used with --from-pr or --from-issue β€” those are a different vibe entirely.
  • --no-workspace β€” skips worktree creation entirely, bestie. Instead it makes a local branch and switches to it right there in your current working directory. It must be used with --from-pr, --from-pr-interactive, --from-issue, or --from-issue-interactive. Cannot be combined with --with-change, --generate, or a positional name argument. And instead of a worktree path, it outputs... the branch name πŸ’…
  • --update-on-existing, -U β€” if the target worktree path already exists, this fast-forwards it to the latest source when that's actually safe to do ✨. Things that get left alone (with a message printed to stderr, though the path is still returned, no stress): worktrees with uncommitted or untracked changes, local commits ahead of the source, or diverged histories. For diverged worktrees the message suggests git rebase and git reset --hard commands so you can resync manually. Local commits are never discarded β€” periodt. For PR/branch worktrees it fetches the remote head and fast-forwards; for issue worktrees it just returns the existing path. Can't be combined with --with-change or --no-workspace.
  • --silent β€” mutes all the progress messages to stderr 🀫. Only the worktree path goes to stdout, unless you're using --output-selection. Ideal for scripting and automation, iykyk.
  • --exec COMMAND β€” runs a post-create command through your current shell environment. It runs in the created worktree directory, or in the current directory when you're using --no-workspace. Shell mode follows $SHELL: zsh uses -ilc, bash uses -ic, and other shells fall back to -lc.
  • --output-selection FILE β€” writes the created worktree path to FILE πŸŽ€. When it's set, stdout output gets suppressed.
  • --note TEXT β€” sets an initial note body on the worktree you just made. The note lives in the configured notes file.
  • --note-file FILE β€” reads the initial note body from FILE. Use - to read from stdin. Mutually exclusive with --note β€” pick a lane 🌸.
  • --description TEXT β€” sets a short description for the worktree. It replaces the directory name in the worktree list for display purposes, and filtering matches against it too πŸ‘‘
  • --tags LIST β€” comma-separated list of tags to attach to the worktree (like bug,frontend). Tags get stored in the note frontmatter and show up as coloured badges in the TUI 🌈.
  • --json β€” outputs the created worktree details as JSON to stdout, while progress messages go to stderr. The JSON object contains path, name, branch, description, and tags fields.
  • --exec-mode MODE β€” controls how the --exec command gets invoked. Valid values:
    • login-shell β€” the default, uses interactive login flags
    • shell β€” non-interactive $SHELL -c
    • direct β€” splits the command string and execs without a shell wrapper

rename πŸ¦‹

Rename a worktree without ever launching the TUI. Iconic, honestly.

It renames the worktree directory. The branch only gets renamed when the current worktree directory name matches the branch name πŸ‘€. Shell completion for the first rename argument suggests available worktree basenames.

Options:

  • NEW-NAME β€” the new worktree name. It's sanitised to lowercase letters, numbers, and hyphens. When it's given as the only argument, the current worktree (detected from the working directory) is what gets renamed.
  • WORKTREE NEW-NAME β€” when you pass two arguments, WORKTREE is the worktree path, branch name, or directory name to rename, and NEW-NAME is the new name. If you pass no arguments at all, lazyworktree lists the available worktrees and prints usage πŸ’€.
  • --silent β€” suppresses all progress messages to stderr. Great for scripting and automation.
  • --json β€” outputs the rename result as JSON to stdout. The JSON object contains old_name, old_path, new_name, and new_path fields. Progress messages go to stderr.

delete πŸ’€

Delete a worktree without launching the TUI.

It auto-detects the current worktree from the working directory. If you're not in a worktree, it shows a selection list. It automatically deletes the associated branch if the worktree directory name matches the branch name. Shell completion for the first delete argument suggests available worktree basenames.

Options:

  • --no-branch β€” skips branch deletion entirely, even if the worktree name matches the branch name. No branch deleting for you πŸ™ˆ
  • --silent β€” suppresses all progress messages to stderr. Useful for scripting and automation.
  • --json β€” outputs the delete result as JSON to stdout. The JSON object contains name, path, and branch_deleted fields. Progress messages go to stderr.

cleanup ✨

Removes merged worktrees, stale branches, and orphaned worktree directories β€” all without launching the TUI.

By default it shows a numbered menu πŸŽ€. You can select entries with individual numbers, comma-separated numbers, ranges, or all. Press Enter with no selection to cancel. Merged local branches that have no worktrees are included when prune_stale_branches is enabled. Dirty merged worktrees stay visible with a warning and get skipped if you select them.

Terminate commands run only before eligible clean merged worktrees are removed. Orphaned directories get revalidated against Git immediately before deletion β€” safety first, always 🫢

Options:

  • --all, --non-interactive β€” attempts every candidate without prompting. Registered worktrees with uncommitted changes are reported and skipped; clean worktrees, stale branches, and orphaned directories remain eligible for cleanup. Worktree status is rechecked immediately before removal. Actual candidate failures cause a non-zero exit, while safety skips do not.
  • --json β€” emits a JSON object to standard output describing the cleanup result, including aggregate counts and a per-item list recording each worktree, its branch, the detection source, whether it was skipped, and whether removal succeeded. Skipped worktrees include a reason. Requires --all. Progress messages β€” including terminate command notices β€” are suppressed.

exec πŸ”₯

Run a command, or trigger a custom command key action, in a worktree straight from the CLI.

The exec subcommand is the bridge between CLI scripting and the TUI's custom command system, so you can execute shell commands or trigger tmux/zellij/herdr sessions without ever entering the TUI. Obsessed.

Options:

  • COMMAND β€” optional positional argument for the shell command to execute. Mutually exclusive with --key. The command runs through your shell environment ($SHELL) with the appropriate flags (zsh -ilc, bash -ic, or fallback -lc).
  • --workspace NAME, -w NAME β€” targets a worktree by name, path, or branch name. If you don't provide one, lazyworktree auto-detects the worktree from the current directory. Shell completion suggests available worktree names ✨
  • --key KEY, -k KEY β€” the custom command key to trigger (e.g., 't' for tmux, 'Z' for zellij, 'H' for Herdr, or '_review' for a palette-only action). Mutually exclusive with the positional command argument. The key must exist in your configuration's custom_commands. Supports shell, tmux, zellij, herdr, and show-output command types. Heads up: new_tab is ignored for tmux and zellij commands, because they have no terminal tab to open β€” their configured attach setting decides whether the session opens in the current terminal or gets created in the background with instructions for attaching later. new_tab is not supported for Herdr or plain commands in CLI mode.
  • --json β€” captures the command output and emits a JSON object containing name, path, command, and exit_code fields. In JSON mode, child stdout is redirected to stderr. Mutually exclusive with --key.

Environment:

The exec command sets the same managed environment variables as custom commands in the TUI: WORKTREE_BRANCH, MAIN_WORKTREE_PATH, WORKTREE_PATH, WORKTREE_NAME, REPO_NAME, REPO_OWNER, REPO_REPONAME, LAZYWORKTREE_TYPE, LAZYWORKTREE_NUMBER, LAZYWORKTREE_TEMPLATE, LAZYWORKTREE_SUGGESTED_NAME, LAZYWORKTREE_TITLE, LAZYWORKTREE_URL, and LAZYWORKTREE_DESCRIPTION 🌸

Contextual LAZYWORKTREE_* values are empty when no PR/MR, issue, or diff source context is known.

note πŸ“

Show or edit worktree notes from the CLI without launching the TUI β€” perfect for scripting, piping, and quick edits.

Subcommands:

  • show [worktree-name] β€” prints the raw note text for a worktree to stdout. Defaults to the worktree detected from the current directory. Suitable for piping.
  • edit [worktree-name] β€” opens the note in $EDITOR (falls back to $VISUAL, then vi). The file uses YAML frontmatter for metadata (icon, color, updated_at) followed by markdown body text.

Options (show):

  • --json β€” outputs the note as a JSON object containing worktree_name, path, note, description, icon, tags, and updated_at fields. Progress messages are written to stderr.

Options (edit):

  • --input FILE, -i FILE β€” reads the note from a file instead of opening an editor. Use - to read from stdin. The input format is the same frontmatter+markdown the editor uses.

doctor 🩺

Reports CLI, repository, and helper tool health for automation.

The doctor command is built for coding agents and scripts. It reports configuration loading, repository detection, worktree visibility, and helper tool availability without needing the whole TUI.

Options:

  • --json β€” outputs the diagnostic result as JSON. Setup gaps are reported in the payload rather than causing a hard failure. No dramatic meltdowns πŸ’…

worktrees 🌈

Discover, resolve, and inspect worktrees with stable machine-readable output.

Subcommands:

  • list β€” lists worktrees for the current repository. Supports --json, --main, --limit, and --no-agent.
  • resolve β€” resolves a worktree name, branch, path, or cwd into a canonical worktree path. Supports --name, --path, --cwd, and --json.
  • get WORKTREE β€” reads one exact worktree by canonical path, branch, or basename. Supports --json.
  • context WORKTREE β€” reads note and agent-session context for one worktree. Supports --include and --json.

notes πŸŽ€

Reads worktree notes with machine-readable output.

Subcommands:

get WORKTREE

Returns note metadata for one exact worktree β€” like, exactly the one you name, no fuzzy matching bestie πŸ’…. Pass --json and you get a stable payload with worktree_name, path, note, description, icon, tags, and updated_at. Script-friendly, chef's kiss πŸŽ€


✨ describe ✨

Emits the CLI command structure as machine-readable JSON. It's literally built for coding agents and scripts that need to discover flags and subcommands without parsing --help output like some kind of animal πŸ¦‹.

Synopsis:

lazyworktree describe [command] [subcommand]
lazyworktree describe --all

Options:

  • --all β€” Describes all commands and their flags. Same energy as passing no arguments at all, fr fr.

🌸 setup-hooks 🌸

Installs agent session hooks for Claude Code, the Codex CLI, and the Copilot CLI. Obsessed with this one πŸ’–.

Synopsis:

lazyworktree setup-hooks [--dry-run]

The setup-hooks command adds lifecycle hooks to ~/.claude/settings.json, $CODEX_HOME/hooks.json (or ~/.codex/hooks.json when CODEX_HOME is unset), and $COPILOT_HOME/hooks/lazyworktree.json (or ~/.copilot/hooks/lazyworktree.json when COPILOT_HOME is unset) so that agent sessions report their state directly to lazyworktree. Hook events give you precise, low-cost session tracking and let Codex CLI and Copilot CLI sessions show up in the agents pane πŸ‘‘

Claude Code and Copilot CLI hooks also report question dialogs, which means lazyworktree can show when an agent is waiting for user input instead of just thinking. Codex CLI does not currently expose an equivalent question-dialog hook 😭.

Existing settings are preserved, and a timestamped backup is written before any modification happens. The command is idempotent β€” run it as many times as you want, it's not gonna snowball on you 🫢.

Hook events are the default liveness source. The former process-table scan (ps/lsof) is deprecated and disabled by default; it may be re-enabled with the agent_sessions.process_scan configuration key.

Heads up bestie πŸ“£: the Codex CLI requires newly installed hooks to be approved with the /hooks command inside a Codex session. And the Copilot CLI loads hook files at startup, so restart any running copilot session after installing. No cap.

Options:

  • --dry-run β€” Shows you the configuration changes without writing any files. Vibes only, zero commitment.

πŸ”₯ EXAMPLES πŸ”₯

πŸ¦‹ Worktree Management

List worktrees (table format):

lazyworktree list

List worktrees (paths only, suitable for scripting):

lazyworktree list --pristine

List worktrees as JSON:

lazyworktree list --json

List only the main branch worktree:

lazyworktree list --main

List only the main branch worktree as JSON:

lazyworktree list --main --json

Inspect CLI and repository health for automation:

lazyworktree doctor --json

Resolve a worktree name to its canonical path:

lazyworktree worktrees resolve --name my-feature --json

Read one exact worktree as JSON:

lazyworktree worktrees get my-feature --json

Read worktree context as JSON:

lazyworktree worktrees context my-feature --json

Read note metadata as JSON:

lazyworktree notes get my-feature --json

Create from current branch (auto-generated name):

lazyworktree create

Create from current branch with explicit name:

lazyworktree create my-feature

Check out an existing branch in its own worktree:

lazyworktree create --from-branch feature/new-feature

Create from specific branch with explicit name:

lazyworktree create --from-branch main my-feature

Create from PR number:

lazyworktree create --from-pr 123

Create from issue number (base: current branch):

lazyworktree create --from-issue 42

Create from issue number with explicit base branch:

lazyworktree create --from-issue 42 --from-branch main

Interactively select a PR (fzf or numbered list):

lazyworktree create -P

Interactively select a PR, pre-filtered:

lazyworktree create -P -q dark

Interactively select an issue, pre-filtered:

lazyworktree create -I --query login

Checkout PR branch without creating a worktree:

lazyworktree create --from-pr 123 --no-workspace

Checkout issue branch without creating a worktree:

lazyworktree create --from-issue 42 --no-workspace

Interactively select issue and checkout without creating a worktree:

lazyworktree create -I --no-workspace

Interactively select PR and checkout without creating a worktree:

lazyworktree create -P --no-workspace

Create with uncommitted changes:

lazyworktree create my-feature --with-change

Create and run a setup command in the new worktree:

lazyworktree create my-feature --exec npm install

Delete worktree (and branch if name matches):

lazyworktree delete

Delete worktree without deleting branch:

lazyworktree delete --no-branch

Choose merged worktrees and other stale entries from a numbered menu:

lazyworktree cleanup

Clean up every candidate without prompting:

lazyworktree cleanup --all

Rename the current worktree (detected from cwd):

lazyworktree rename new-feature

Rename a worktree by name:

lazyworktree rename feature new-feature

Rename by path:

lazyworktree rename /path/to/worktree new-worktree

Run a command in a specific worktree:

lazyworktree exec --workspace=my-feature make test

Run a command in the current worktree (auto-detected):

lazyworktree exec npm run build

Trigger a tmux custom command:

lazyworktree exec --key=t --workspace=my-feature

Trigger a zellij custom command in the current worktree:

lazyworktree exec -k Z

Trigger a Herdr custom command:

lazyworktree exec -k H -w my-feature

πŸŽ€ Notes

Show the note for the current worktree:

lazyworktree note show

Show the note for a specific worktree:

lazyworktree note show my-feature

Edit a worktree note in your editor:

lazyworktree note edit my-feature

Set a note from stdin:

echo release prep | lazyworktree note edit -i -

Set a note from a file:

lazyworktree note edit my-feature -i note.md

⭐ TUI Launch

Launch the TUI (default):

lazyworktree

Launch with custom worktree directory:

lazyworktree --worktree-dir ~/worktrees

Override theme via CLI:

lazyworktree --config lw.theme=nord

🌈 Shell Completion

Generate zsh completion:

lazyworktree --completion zsh > ~/.zsh/completions/_lazyworktree

Generate bash completion:

lazyworktree --completion bash > /etc/bash_completion.d/lazyworktree

Generate fish completion:

lazyworktree --completion fish > ~/.config/fish/completions/lazyworktree.fish

πŸ‘‘ KEY BINDINGS πŸ‘‘

🧭 General Navigation

  • j, k, Up, Down β€” Navigate between items in the focused pane or selection menus. Baby's first muscle memory πŸ’…
  • Home β€” Go to first item in focused pane.
  • End β€” Go to last item in focused pane.
  • h, l β€” Shrink/grow worktree pane (h=shrink, l=grow).
  • Tab, ] β€” Cycle to next pane.
  • [ β€” Cycle to previous pane.
  • 1 β€” Switch to Worktree pane (toggle zoom if already focused).
  • 2 β€” Switch to Status pane (toggle zoom if already focused).
  • 3 β€” Switch to Git Status pane (toggle zoom if already focused).
  • 4 β€” Switch to Commit pane (toggle zoom if already focused).
  • 5 β€” Switch to Notes pane (toggle zoom if already focused). Only visible when the selected worktree has a note πŸŽ€
  • 6 β€” Switch to Agent Sessions pane (toggle zoom if already focused). Only visible when open Claude, Codex, Copilot, or pi sessions are attached to the selected worktree by default. Pressing 6 also reveals historical matching sessions when nothing is currently open; pressing A inside the pane toggles between open-only and all matching sessions. Tab cycling includes this pane last when visible. Iconic behaviour tbh πŸ‘‘
  • = β€” Toggle zoom for focused pane (full screen, press again to unzoom).
  • L β€” Toggle layout between default (worktrees left with agent sessions and notes below when present, status/git status/commit stacked right) and top (worktrees full-width top, optional agent sessions and notes rows below, status/git status/commit side-by-side bottom).
  • y β€” Copy to clipboard (context-aware: worktree path in worktrees pane, file path in git status pane, commit SHA in commit pane). Uses OSC52, works over SSH. Slay πŸ¦‹
  • Y β€” Copy the selected worktree's branch name to the clipboard.
  • ? β€” Show help screen.
  • q β€” Quit application.

πŸ› οΈ Worktree Operations

  • Enter β€” Jump to worktree (exit and cd), or open commit file tree in commit pane.
  • d β€” Show diff in pager for the selected worktree (or selected commit in the commit pane).
  • c β€” Create new worktree (from branch, commit, PR/MR, or issue).
  • i β€” Open selected worktree notes. If a note exists, a viewer opens first; if no note exists, the editor opens directly.

In the note viewer, j/k (or arrow keys) scroll, Ctrl+D/Ctrl+U move half a page, g/G jump to top/bottom, e opens edit mode, and q/Esc closes. In the note editor, Ctrl+S saves, Ctrl+X opens the note in an external editor, Enter inserts a new line, and Esc cancels. Worktrees with non-empty notes show a note marker beside the name.

Set custom worktree icon is available in the TUI and command palette for quick task labelling, with curated icons for categories such as feature work, bugs, TODOs, reviews, refactors, blocked work, and releases. Set worktree colour and Set worktree description are available from the command palette only. The description is a short label that replaces the directory name in the worktree list for display purposes; the filter and search also match against descriptions. Set worktree colour stores a colour in notes/frontmatter and applies it to the worktree name; use the picker for named colours and 256 indices, or choose Custom for a hex value or another supported named colour.

Set worktree tags lets you type labels separated by commas when you want multiple tags (for example, "bug,frontend,urgent") and toggle existing labels in the same editor; tags display as coloured badges (guillemet brackets) after the worktree name and in the Info pane when present, each receiving a deterministic colour from the theme palette. Browse by worktree tags in the command palette to list existing labels with counts and apply an exact tag:<name> worktree filter. Tags are stored in notes/frontmatter and are included in filter and search matching 🌸

In the Info pane, notes render Markdown for headings, bold text, inline code, lists, quotes, links, and fenced code blocks. Uppercase note tags such as TODO, FIXME, or WARNING: are highlighted with icons outside fenced code blocks. Lowercase tags are left unchanged.

  • T β€” Opens the Taskboard, a Kanban-lite board grouped by worktree πŸ—‚οΈ that reads markdown checkbox items straight out of your notes (like - [ ] draft release notes). Use j/k to move around, Enter or Space to toggle a task done/not done, a to add a new task, f to filter tasks, and q/Esc to close it. Honestly addictive, be warned πŸ’…
  • m β€” Rename the selected worktree. Live your rebrand era ✨
  • D β€” Delete the selected worktree. Gone, but like, respectfully 🫢
  • A β€” Absorb a worktree into main β€” merges or rebases depending on your configuration. Main-character absorption fr πŸ‘‘
  • X β€” Prune merged worktrees and stale branches. It auto-refreshes your PR/MR data from GitHub or GitLab (if you're connected to one), then sniffs out worktrees whose associated PR has been merged, or whose branch got merged into the main branch. For repos with no GitHub/GitLab remotes it just uses git-based merge detection, no cap. When prune_stale_branches is enabled it also throws in local branches that are merged but have no associated worktree. Then it shows you a checklist so you pick exactly which items get removed πŸ§ΉπŸ’€
  • ! β€” Run an arbitrary shell command in the selected worktree. Chaos mode: unlocked πŸ”₯
  • r β€” Refresh the worktree list (and also refreshes PR/MR/CI for the current worktree on GitHub/GitLab). The little refresh slay πŸŒ€
  • R β€” Fetch all remotes. All of them. No favourites 🫑
  • S β€” Synchronise with upstream: git pull, then git push, current branch only, requires a clean worktree, and honours merge_method. Very demure, very synchronised πŸŽ€
  • P β€” Push to the upstream branch. Current branch only, requires a clean worktree, and it prompts you to set upstream when it's missing. Push it, push it πŸ¦‹
  • s β€” Cycle the sort mode (Path / Last Active / Last Switched). Reorganise your whole vibe πŸ”„

🩺 Status Pane ✨

The Status pane is where your PR info, CI checks, notes, and divergence status live. And bestie β€” in terminals that support OSC-8 hyperlinks, the PR/MR number in the info panel is actually clickable πŸ–±οΈβœ¨

  • j, k β€” Navigate CI checks (when they're visible).
  • Enter β€” Open the selected CI check URL in your browser 🌐
  • Ctrl+v β€” View the selected CI check logs in a pager. Reading era πŸ“–
  • Ctrl+r β€” Restart the CI job (GitHub Actions only). Rerun, bestie πŸ”

🌳 Git Status Pane πŸ’š

The Git Status pane shows your changed files in a collapsible tree view, grouped by directory. Directories get expand/collapse indicators and you toggle them with Enter. Files are sorted alphabetically within each directory level, and they come with icons from the selected icon set when that's enabled πŸŽ€

  • Enter β€” Toggle a directory's expand/collapse, or show the diff for the selected file.
  • s β€” Stage/unstage the selected file or directory. Stage it or un-stage it, your call πŸ’…
  • D β€” Delete the selected file or directory (with a confirmation, because we're not reckless).
  • e β€” Open the selected file in your editor ✍️
  • c β€” Commit staged changes. Press Ctrl+G from anywhere, or c in the status pane, to open the commit screen. The commit screen keeps the subject on its own line with the body beneath it, Enter moves you from subject to body, Ctrl+S saves, Ctrl+O auto-generates, and Ctrl+X opens the draft in your configured external editor when one's available. Chef's kiss commit flow πŸ˜­πŸ‘‘
  • C β€” Stage all changes and commit. All of it, at once, iconic πŸ’₯
  • d β€” Show the full diff (all files) in a pager.
  • ctrl+Left, ctrl+Right β€” Jump to the previous/next folder. Zoom around the tree 🌲
  • / β€” Search file or directory names (incremental, so it updates as you type).
  • Ctrl+d, Space β€” Half page down.
  • Ctrl+u β€” Half page up.
  • PageUp, PageDown β€” Half page up/down. Two ways to do the exact same thing, and honestly? Love that for us 🫢

πŸ“œ Commit Pane 🧾

  • j, k, Up, Down β€” Move between commits.
  • Enter β€” Open the commit file tree view.
  • d β€” Show the full commit diff in a pager.
  • C β€” Cherry-pick the commit to another worktree (with an interactive picker). Cherry on top, literally πŸ“
  • ctrl+j β€” Move to the next commit and open the commit file tree. Two steps, one keystroke ✨
  • / β€” Search commit titles (incremental).

πŸ“ Commit File Tree 🌸

So when you press Enter on a commit in the commit pane, a file tree view opens showing all the files changed in that commit. The tree is collapsible by directory and includes icons from the selected icon set when enabled. It's giving organised πŸŽ€

  • j, k, Up, Down β€” Navigate files and directories.
  • Enter β€” Toggle directory expand/collapse, or show the diff for the selected file.
  • d β€” Show the full commit diff in a pager.
  • f β€” Filter files by name.
  • / β€” Search files (incremental).
  • n, N β€” Next/previous search match.
  • Ctrl+d, Space β€” Half page down.
  • Ctrl+u β€” Half page up.
  • g, G β€” Jump to top/bottom.
  • q, Esc β€” Return to the commit log. Bye bestie πŸ‘‹
  • f β€” Filter the focused pane by fuzzy matching. When a filter is active, the pane title shows a filter indicator with an [Esc] Clear hint. Filtering narrows the visible items down to whatever matches your input. In selection menus, press f to show the filter input, and Esc returns to the list while keeping the current filter. Fuzzy match my heart πŸ’–
  • Alt+n, Alt+p β€” Move the selection and fill the filter input (worktree filter only). Fancy.
  • Up, Down, Ctrl+j, Ctrl+k β€” Move the selection without changing the filter input.
  • / β€” Search the focused pane incrementally. Unlike filter, search highlights matches while keeping all items visible. Use n/N to bounce between matches.
  • n β€” Jump to the next search match.
  • N β€” Jump to the previous search match.
  • Esc β€” Clear the filter for the focused pane (when a filter is active).

πŸ™ Forge Integration 🌈

  • v β€” View CI checks (Enter opens one in the browser, Ctrl+v views logs in a pager).
  • Enter β€” Open the selected CI job in your browser (within CI check selection).
  • Ctrl+v β€” View the selected CI check logs in a pager (within CI check selection).
  • Ctrl+r β€” Restart the selected CI job (GitHub Actions only, within CI check selection).
  • o β€” Open the PR/MR in your browser (or the root repository in your editor if you're on the main branch with a merged/closed/no PR). Either way you're going somewhere 🌐

πŸŽ›οΈ Command Palette πŸ‘‘

  • f1, ctrl+p, : β€” Open the command palette showing all available commands (like select theme, or open the commit screen) πŸͺ„

The palette exposes a "Create from current" entry which copies the branch you currently occupy. When uncommitted files exist, the prompt shows an "Include current file changes" checkbox β€” Tab/Shift+Tab focuses it and Space toggles it. When it's selected, the diff gets passed to any configured branch_name_script for naming suggestions. Naming inspo, delivered ✨

Custom commands whose config key starts with _ appear in the command palette only and do not bind a direct TUI shortcut. Palette-exclusive, lowkey mysterious πŸ’…

The command palette automatically lists active tmux and zellij sessions and Herdr workspaces and sessions under separate "Active ..." sections that appear after the Multiplexer section β€” so you can quickly switch to existing sessions without manually typing session names. Herdr workspaces are listed from the current or default Herdr session only. tmux sessions and Herdr workspaces are tagged with the worktree they belong to: they're listed whatever their name, shown under their full name with the worktree as description, and found again by that tag when the session name template changes. zellij sessions, Herdr sessions and untagged sessions from earlier releases are listed only when they start with the configured session prefix (default: wt-), which you can customise via the session_prefix configuration option. A session name already used by another worktree's session is reported as a conflict rather than reused or killed β€” no stealing, no murdering πŸ’€. Also note that tmux does not permit colons (:) in session names, so any colons in the prefix get automatically converted to hyphens (-). The more you know 🌈

🧚 LazyGit ✨

  • g β€” Open LazyGit for the currently selected worktree. Git, but make it lazy and cute πŸŽ€

πŸ’‘ Tips πŸ“

The loading modal shows contextual tips based on the current operation (for example refresh, fetch, synchronise, push, or CI rerun). When multiple tips are available for the same context, lazyworktree avoids showing the same tip consecutively β€” no repeats, we respect your attention span πŸ’…

Curated tips are also listed in the in-application Help screen under "Tips & Shortcuts". Go read them, they're good 🫢


πŸ–±οΈ MOUSE SUPPORT 🐭

lazyworktree provides comprehensive mouse support for improved navigation and interaction. Yes bestie, you can click things. Groundbreaking, I know 😭

  • Click on pane β€” Focus the clicked pane (Worktree, Status, Git Status, Commit, Notes, or Agent Sessions).
  • Click on item β€” Select the clicked item in a table or list.
  • Mouse wheel β€” Scroll up or down in the focused pane.

Mouse support works in all panes and is particularly useful for quickly switching context or selecting specific items without keyboard navigation. Your wrist says thank you 🫢✨

🐚 SHELL INTEGRATION πŸ’œ

To enable the "jump" functionality, source the helper functions from the installation:

For source builds:

source /path/to/lazyworktree/shell/functions.zsh
jt() { worktree_jump ~/path/to/your/main/repo "$@"; }

For Homebrew:

source $(brew --prefix)/opt/lazyworktree/share/lazyworktree/functions.zsh
jt() { worktree_jump ~/path/to/your/main/repo "$@"; }

With completion (zsh):

_jt() { _worktree_jump ~/path/to/your/main/repo; }
compdef _jt jt

🌸 BRANCH NAMING CONVENTIONS 🌸

ok so when you're making worktrees with manual branch names, lazyworktree does a little glow-up on your special characters β€” it auto-converts them to hyphens πŸ’…. Why? Because Git and terminal multiplexers (tmux / zellij / herdr) need names they can actually handle. This keeps your worktree directories AND your session names valid across, like, every system. no cap.

✨ Supported Characters

branch names can contain:

  • Letters (a-z, A-Z)
  • Numbers (0-9)
  • Hyphens (-)

everything else gets automatically turned into a hyphen. that's it, that's the whole guest list πŸŽ€

✨ Character Conversions

  • feature.new β†’ feature-new β€” dots are not allowed in Git branch names
  • bug fix here β†’ bug-fix-here β€” spaces are not allowed in branch names
  • path/to/branch β†’ path-to-branch β€” slashes are reserved for path separators
  • feature:test β†’ feature-test β€” colons are reserved in tmux/zellij/herdr session names
  • test_underscore β†’ test-underscore β€” normalised to hyphens for consistency

✨ Rules

  • leading and trailing hyphens are removed
  • consecutive hyphens are collapsed into a single hyphen
  • maximum length: 50 characters (manual input), 100 characters (auto-generated names)

πŸ¦‹ CONFIGURATION πŸ¦‹

ok bestie, lazyworktree pulls config from a whole stack of places, and here's the pecking order β€” highest priority first, no exceptions:

  1. CLI overrides (using the --config flag)
  2. Git local configuration (repository-specific via git config --local)
  3. Git global configuration (user-wide via git config --global)
  4. YAML configuration file (~/.config/lazyworktree/config.yaml or config.yml)
  5. Built-in defaults

πŸ’œ Git Configuration

you can stash settings right inside Git's own config system using the lw. prefix. iconic behaviour tbh:

# Set globally
git config --global lw.theme nord
git config --global lw.worktree-dir ~/.local/share/worktrees

# Set per-repository (overrides global)
git config --local lw.theme dracula
git config --local lw.worktree-dir '$LWT_REPO_PATH/.worktrees'  # Repo-local worktrees
git config --local lw.init-commands "link_topsymlinks"
git config --local lw.init-commands "npm install"  # Multi-values supported

# View all lazyworktree settings
git config --global --get-regexp "^lw."
git config --local --get-regexp "^lw."

this is so flexible β€” you can set a default theme globally and then override it per-repository, or temporarily flip settings from the command line with --config. chef's kiss 🫢

lazyworktree also reads ~/.config/lazyworktree/config.yaml (or config.yml) for your default settings. an example config is down below (and you can also find it in config.example.yaml).

🌈 General Settings

  • worktree_dir β€” the default worktree root directory. worktrees live under ~/.local/share/worktrees/<repo_name> by default. the app tries to resolve repo locations via gh repo view or glab repo view. if it can't detect the repository name, lazyworktree falls back to a local local-<hash> key for cache and last-selected storage. Default: ~/.local/share/worktrees Can be overridden with --worktree-dir.
  • worktree_notes_path β€” optional path to store all worktree notes in one single shared JSON file. when it's set, notes are keyed by repository/worktree-relative identifiers instead of absolute paths, which helps loads with synchronising across systems. when worktree_note_type is splitted, this field is treated as a path template. available variables: $REPO_NAME (owner/repo combined), $REPO_OWNER, $REPO_REPONAME, and $WORKTREE_NAME. Default: empty (per-repository .worktree-notes.json files under worktree_dir)
  • worktree_note_type β€” the note storage type. options: onejson (single JSON file, default), splitted (individual markdown files with YAML frontmatter). in splitted mode, each worktree note gets stored as a separate markdown file at the path specified by worktree_notes_path. Default: onejson
  • sort_mode β€” default sort order for worktrees. options: path (alphabetical), active (last commit date), switched (last accessed). Default: switched Note: the old sort_by_active option is still supported for backwards compatibility.
  • layout β€” pane arrangement. options: default (worktrees on the left with optional agent sessions and notes below, status/log stacked on the right), top (worktrees full-width along the top, optional agent sessions and notes rows below, status/log side-by-side at the bottom). Default: default Can be toggled at runtime with the L key.
  • layout_sizes β€” configurable pane size weights. each sub-key accepts an integer between 1 and 100. values are relative weights that get normalised at computation time, so info: 30, git_status: 30, commit: 30 means each one receives one-third of the secondary area. omitted fields keep their built-in defaults. focus-based dynamic resizing still applies on top of the configured baseline. sub-keys: worktrees (main pane width or height), info, git_status, commit, agent_sessions, notes. Default: unset (built-in ratios used).
  • search_auto_select β€” start with filter focused and select the first match on Enter. Default: false Can also be enabled with --search-auto-select.
  • auto_refresh β€” refresh git metadata and working tree status in the background. Default: true Set it to false to rely on manual refresh (r).
  • refresh_interval β€” background refresh interval in seconds. Default: 10 Set to 0 to disable timed refreshes.
  • ci_auto_refresh β€” periodically refresh CI status for GitHub repositories. when enabled, CI checks are refreshed automatically for open PRs or branches with pending CI jobs. disabled by default because it eats the GitHub API rate limit (5,000 requests/hour for authenticated users). πŸ’€ Default: false
  • ci_remote β€” the git remote to target for CI and PR status queries on GitHub. when unset or set to auto, an upstream remote is preferred when present, otherwise origin. set it to a remote name (for example origin) to target a specific remote without changing repository identity. GitLab MR/CI queries keep using glab's own repository resolution. super useful for fork workflows where pull requests live on the upstream repository 🧚 Default: auto
  • disable_pr β€” disable all PR/MR fetching and display. when enabled, no GitHub/GitLab API calls are made for PR/MR data, the PR column is hidden from the worktree table, and CI status fetching is disabled. handy for repositories that aren't hosted on GitHub or GitLab, or when you just don't want PR/MR integration. Default: false
  • debug_log β€” path to a debug log file for troubleshooting. when set, detailed debug info gets written to this file. Can also be set with --debug-log.

πŸŽ€ User Interface

  • theme β€” the UI colour theme. if it's left empty or unspecified, the theme is auto-detected from your terminal background. available built-in themes: dracula (default for dark), dracula-light (default for light), narna, clean-light, catppuccin-latte, rose-pine-dawn, one-light, everforest-light, everforest-dark, solarized-dark, solarized-light, gruvbox-dark, gruvbox-light, nord, monokai, catppuccin-mocha, modern, tokyo-night, one-dark, rose-pine, ayu-mirage, kanagawa. custom themes can be defined in the configuration file (see custom_themes below) and will show up right alongside the built-in themes in the theme selection screen. Can also be set with --theme.
  • icon_set β€” choose your icon set. options: "nerd-font-v3", "text". applies to file icons, UI indicators, and help headings. the text set uses plain labels. if your icons render as placeholder glyphs, set icon_set to text or install a font patched with Nerd Font πŸ™ˆ. Default: "nerd-font-v3"
  • pr_reviewers β€” this one controls the reviewer line in the Info pane bestie ✨ Options: "auto" and "never".

With "auto", the Info pane spills the tea on how many people have submitted a review on the selected pull or merge request, and lists them all out β€” each one tagged as approved, changes requested, or reviewed without a verdict. Reviewers who got asked but haven't answered yet? Not counted, we don't do ghosting math πŸ’… Bots carry a bot icon; everybody else carries an avatar where avatar badges are available. With "never", the line is just gone β€” along with the forge request that would've populated it, so it's giving efficiency.

Reviewers are fetched for the selected worktree only, from GitHub or GitLab, and the result gets reused for a minute.

Default: "auto"

  • fuzzy_finder_input β€” turns on fuzzy finder suggestions in input dialogues πŸ¦‹ When it's enabled, typing in text input fields shows you fuzzy-filtered suggestions pulled from the available options. Arrow keys to navigate the suggestions, Enter to select. It's giving "autocomplete but make it cute".

Default: false

  • palette_mru β€” enables MRU (Most Recently Used) sorting for the command palette πŸ”₯ When enabled, a "Recently Used" section pops up at the top of the palette showing your most frequently used commands β€” main character commands only. Usage history is stored per-repository in .command-palette-history.json.

Default: true

  • palette_mru_limit β€” how many recent commands show up in that command palette MRU section. Only applies when palette_mru is enabled, obviously.

Default: 8

  • max_untracked_diffs β€” max number of untracked file diffs to show. Set it to 0 to hide untracked diffs entirely.

Default: 10

  • max_diff_chars β€” max characters to display in diffs before truncation kicks in. Set it to 0 to disable truncation completely (chaotic, but valid).

Default: 200000

  • max_name_length β€” max length for worktree names displayed in the table. Names longer than this get truncated with "..." appended. Set it to 0 to disable truncation entirely.

Default: 95

πŸ’œ Diff and Pager πŸ’œ

  • git_pager β€” the diff formatter/pager command. Default is delta. Set it to an empty string to disable diff formatting and use plain git diff output. You can also use alternatives such as diff-so-fancy, no gatekeeping here.

VS Code girlies can set git_pager: code to open diffs via git difftool with proper side-by-side comparison (one window per file) πŸŽ€

Default: delta

  • git_pager_args β€” list of extra arguments passed to git_pager for diff rendering. If you omit it and git_pager is delta, lazyworktree picks a syntax theme matching the final UI theme, including CLI theme overrides. The default mappings, in all their glory:

dracula -> Dracula, dracula-light -> Monokai Extended Light, narna -> OneHalfDark, clean-light -> GitHub, catppuccin-latte -> Catppuccin Latte, rose-pine-dawn -> GitHub, one-light -> OneHalfLight, everforest-light -> Gruvbox Light, solarized-dark -> Solarized (dark), solarized-light -> Solarized (light), gruvbox-dark -> Gruvbox Dark, gruvbox-light -> Gruvbox Light, nord -> Nord, monokai -> Monokai Extended, catppuccin-mocha -> Catppuccin Mocha.

Use --show-syntax-themes to display default values.

  • git_pager_interactive β€” enables interactive diff viewers that need terminal control (default: false). Set it to true for tools like diffnav, ftdv, or tig that give you keyboard navigation 🧚

When enabled, only unstaged changes (git diff) are shown. Non-interactive formatters like delta and diff-so-fancy should keep this false.

  • git_pager_command_mode β€” enables command-based diff viewers that run their own git commands (default: false). Set it to true for tools like lumen that accept subcommands such as lumen diff, lumen diff <commit>.

When enabled, lazyworktree calls <git_pager> diff [args...] directly instead of piping git diff output. Iconic behaviour fr.

  • pager β€” the pager command for show_output custom commands and the diff viewer. Default: $PAGER, fallback: less --use-color --wordwrap -qcR -P 'Press q to exit..', then more, then cat. A whole little ladder of options πŸ’«

When the pager is less, lazyworktree configures LESS= and LESSHISTFILE=- to ignore your user defaults. No cap.

  • ci_script_pager β€” pager command for CI check logs. When it's set, it runs interactively with direct terminal control (no set -o pipefail, no environment adjustments). Falls back to pager if it's not configured.

Example: ci_script_pager: "less -R" or ci_script_pager: "bat --style=plain"

CI-specific environment variables available to ci_script_pager:

  • LW_CI_JOB_NAME β€” name of the CI check/job
  • LW_CI_JOB_NAME_CLEAN β€” sanitised job name suitable for filenames (lowercase, hyphens only)
  • LW_CI_RUN_ID β€” GitHub Actions run ID (extracted from check URL)
  • LW_CI_STARTED_AT β€” job start time in ISO 8601 format (if available)
  • editor β€” editor command for Status pane file edits. Default: config value, then $EDITOR, then nvim, then vi. The classic chain, we love to see it 🫢

🌈 Forge Integration 🌈

  • issue_branch_name_template β€” the template for branch names created from GitHub/GitLab issues ✨ Available placeholders: {number} (issue number), {title} (sanitised issue title), and {generated} (generated title from branch_name_script, falls back to {title} if unavailable).

Default: issue-{number}-{title}

Example: with template "fix-{number}-{title}", issue #42 titled "Memory Leak" becomes branch "fix-42-memory-leak". With template "issue-{number}-{generated}" and a script configured, the generated title is used instead. Slay.

  • pr_branch_name_template β€” the template for worktree names created from pull/merge requests πŸ’… Available placeholders: {number} (PR/MR number), {title} (sanitised PR/MR title), {pr_author} (sanitised PR author username), and {generated} (generated title from branch_name_script, falls back to {title} if unavailable).

Default: pr-{number}-{title}

Example: with template "review-{number}", PR #123 becomes worktree name "review-123". With template "pr-{number}-{pr_author}-{generated}", PR #123 by alice can become "pr-123-alice-feat-fix-ci".

πŸ”’ Security and Behaviour πŸ”’

  • trust_mode β€” security setting for executing commands from .wt files. Options:
    • tofu (default β€” prompts you on first use/change)
    • never (never run commands)
    • always (always run without prompting, risky β€” like, genuinely, we said what we said πŸ’€)
  • merge_method β€” merge method for the "Absorb worktree" and "Synchronise with upstream" actions. Options:
    • rebase (default β€” rebases onto main then fast-forwards; synchronise uses git pull --rebase=true)
    • merge (creates a merge commit and uses a standard git pull)

⚑ Automation ⚑

  • branch_name_script β€” script to generate branch name suggestions when creating worktrees from changes, issues, or PRs πŸŽ€ For issues/PRs, the script should output a title (e.g., "feat-ai-session-manager") which then gets sanitised and made available via the {generated} placeholder in your configured template. For diffs, the script should output a complete branch name. The script receives content on stdin (git diff for changes, issue/PR title and body for issues/PRs). First line of output is used.

Available environment variables: LAZYWORKTREE_TYPE (pr/issue/diff), LAZYWORKTREE_NUMBER (PR/issue number), LAZYWORKTREE_TEMPLATE (configured template), LAZYWORKTREE_SUGGESTED_NAME (template-generated name using original PR/issue title).

Script operates under a 30-second timeout β€” so don't be doing the absolute most in there ⏳
  • worktree_note_script β€” script to generate an initial worktree note when creating from a PR/MR or issue πŸ“ The script receives title and body content on stdin and may return multiline output. If the script fails or returns empty output, worktree creation continues and no note is saved β€” no stress, it's not a blocker.

Available environment variables: LAZYWORKTREE_TYPE (pr/issue), LAZYWORKTREE_NUMBER, LAZYWORKTREE_TITLE, LAZYWORKTREE_URL, LAZYWORKTREE_DESCRIPTION.

Script operates under a 30-second timeout.
  • init_commands β€” list of commands to execute when creating a worktree. These execute before any repository-specific .wt commands (if present).

Available environment variables: WORKTREE_BRANCH, MAIN_WORKTREE_PATH, WORKTREE_PATH, WORKTREE_NAME, REPO_NAME, REPO_OWNER, REPO_REPONAME, LAZYWORKTREE_TYPE, LAZYWORKTREE_NUMBER, LAZYWORKTREE_TEMPLATE, LAZYWORKTREE_SUGGESTED_NAME, LAZYWORKTREE_TITLE, LAZYWORKTREE_URL, LAZYWORKTREE_DESCRIPTION. Contextual LAZYWORKTREE_* values are empty when no PR/MR, issue, or diff source context is known.

Special built-in command: link_topsymlinks β€” and no, it's not a shell command, it's a whole vibe. It symlinks untracked/ignored files from the main worktree root, non-empty editor configs (.vscode, .idea, .cursor, .claude/settings.local.json) if it exists, ensures the tmp/ directory exists, and runs direnv allow if .envrc is present. Genuinely obsessed with this one πŸ’–

  • use_herdr_for_workspace_create β€” let Herdr create new worktrees with herdr worktree create, so each one opens as a workspace grouped under its repository ✨ Applies to the CLI, the TUI and the shell helpers. See Creating worktrees with Herdr below.

Default: false

  • herdr_workspace_create_layout β€” custom command key whose herdr block lays out workspaces created with use_herdr_for_workspace_create, resolved as when pressing the key in the worktrees pane. Empty uses Herdr's plain workspace.

Default: none

  • terminate_commands β€” list of commands to execute when removing a worktree. These execute before any repository-specific .wt commands (if present). Same environment variables as init_commands.
  • custom_commands β€” custom keybindings to run commands in the selected worktree 🌸 Commands execute interactively (the TUI suspends, like lazygit) and appear in the command palette. Prefix a command key with _ to make it command-palette only. Key-bound custom commands take precedence over built-in keys. The default bindings are t for tmux, Z for Zellij, and H for Herdr.

Each custom command supports the following fields:

  • command: The command to execute (required, unless using tmux/zellij/herdr)
  • description β€” the little blurb that shows up in the help screen and command palette (optional)
    • show_help β€” show it in the help screen and footer hints (default: false)
    • wait β€” wait for a key press after the command finishes (default: false, and honestly it's kind of a lifesaver for quick commands)
    • show_output β€” run non-interactively and dump stdout/stderr into the pager (default: false, and heads up: it ignores wait entirely)
    • new_tab β€” launch the command in a brand-new terminal tab (default: false). Can be combined with tmux, zellij, and herdr to open multiplexer sessions in new tabs instead of suspending the TUI. Currently supports Kitty (remote control has to be enabled for this, bestie), WezTerm, and iTerm. Inside Herdr, workspace mode focuses the workspace in place instead of opening a new tab. πŸ’…
    • tmux β€” configure a tmux session instead of executing a single command (object, optional)
    • zellij β€” configure a zellij session instead of executing a single command (object, optional)
    • herdr β€” configure a Herdr workspace or session instead of executing a single command (object, optional)
    • container β€” run inside an OCI container (object, optional). Giving just an image uses its defaults. Can be combined with tmux, zellij, or herdr to run each window/tab inside a container.

Supported key formats, bestie: single keys (e, s, t), modifier combos (ctrl+e, alt+t), special keys (enter, esc, tab, space), and palette-only identifiers prefixed with _ (for example _review). πŸŽ€

Environment variables available in custom commands: WORKTREE_BRANCH, MAIN_WORKTREE_PATH, WORKTREE_PATH, WORKTREE_NAME, REPO_NAME, REPO_OWNER, REPO_REPONAME, LAZYWORKTREE_TYPE, LAZYWORKTREE_NUMBER, LAZYWORKTREE_TEMPLATE, LAZYWORKTREE_SUGGESTED_NAME, LAZYWORKTREE_TITLE, LAZYWORKTREE_URL, LAZYWORKTREE_DESCRIPTION. Contextual LAZYWORKTREE_* values are empty when no PR/MR, issue, or diff source context is known. (No context, no tea. 🍡)

For tmux sessions, the following fields are available:

  • session_name β€” tmux session name, supports env vars (default: #<number> <title> for pull request worktrees, shortened to 40 characters, otherwise the worktree name). Colons, slashes, and backslashes are replaced with -. ✨
  • attach β€” if true, attach/switch immediately; if false, show info modal (default: true)
  • on_exists β€” behaviour if the session exists: switch, attach, kill, new (default: switch)
  • windows β€” list of window definitions. Each window has: name (supports env vars), command (empty uses default shell), cwd (default: $WORKTREE_PATH). If omitted, creates a single shell window.

For zellij sessions, the following fields are available:

  • session_name β€” zellij session name, supports env vars (default: wt:$WORKTREE_NAME). Colons, slashes, and backslashes are replaced with -.
  • attach β€” if true, attach immediately; if false, show info modal (default: true)
  • on_exists β€” behaviour if the session exists: switch, attach, kill, new (default: switch)
  • windows β€” list of tab definitions. Each tab has: name (supports env vars), command (empty uses default shell), cwd (default: $WORKTREE_PATH). If omitted, creates a single shell tab.

For Herdr workspaces or sessions, the following fields are available:

  • mode β€” workspace (default) creates one workspace per worktree inside an existing Herdr session; session creates a named Herdr session per worktree.
  • session_name β€” workspace label in workspace mode, session name in session mode, supports env vars (default label: #<number> <title> for pull request worktrees, shortened to 40 characters, otherwise the worktree name; default session name: the session prefix followed by the worktree name). Herdr session names are limited to letters, digits, ., _, and -, with a maximum of 64 bytes; other characters are replaced with -. A name that sanitises to nothing is rejected instead of falling back to the default session. Workspace labels are used exactly as configured. 🫢
  • herdr_session β€” Herdr session hosting the workspace (workspace mode only; empty inherits the current session, default targets the default session explicitly).
  • attach β€” if true, attach or focus immediately; if false, show info modal (default: true). A reused workspace is focused before the external client attaches. Herdr cannot switch sessions from inside a running client, so session mode shows attach instructions when nested, including when the session already exists. In workspace mode, a session other than the current one shows attach instructions instead of focusing a workspace the current client cannot display.
  • on_exists β€” behaviour if the workspace or session exists: switch, attach, kill, new (default: switch). For Herdr, attach and switch both reuse the existing workspace or session.
  • Partial layouts β€” Herdr offers no reliable completion marker. A retry reuses a workspace left behind by a failed attempt and does not re-run the configured commands, so a partially configured workspace can be reused as-is. Use on_exists: kill to discard it and build a fresh layout; on_exists: new instead builds a fresh workspace under an incremented label and leaves the old one in place. In session mode, these options apply when the session is running; reusing a running session ensures the worktree workspace exists, creating it when the previous attempt never got that far.
  • Normalised names β€” in session mode, on_exists: kill and on_exists: new act on the normalised session name, because Herdr sessions carry no worktree tag. When sanitisation makes two worktrees share a session name, killing or incrementing one can target the other worktree's session. Workspaces are found by their worktree tag instead. 😭 (this one's a genuine gotcha, ngl)
  • worktree_group β€” if true, open workspaces with herdr worktree open so they nest under their repository in the Herdr sidebar (default: true). Herdr opens the repository's main workspace when needed, and the repository is trusted. Bare repositories, or a refusal from Herdr, fall back to a plain workspace. Grouped workspaces are also matched by checkout path, so workspaces opened from Herdr's own picker are reused, listed and killed like tagged ones. Set to false for plain workspaces.
  • Deleting worktrees β€” deleting a worktree from the TUI, delete or cleanup closes its Herdr workspaces in every running Herdr session, stopping their programmes. The workspace hosting lazyworktree is left open. πŸ”₯

Creating worktrees with Herdr: set use_herdr_for_workspace_create to let herdr worktree create make every new worktree (CLI, TUI and shell helpers), grouped under its repository. Off by default. herdr_workspace_create_layout names a custom command key whose herdr block lays out the workspace, resolved as in the worktrees pane; its worktree_group is ignored. Without it, Herdr's plain workspace is used. herdr_session sees only worktree, branch and repository variables at that point. lazyworktree still prepares branches; a Herdr failure falls back to Git. The layout is applied after the init commands; a rolled-back worktree closes its workspace, and a workspace that was already open is never laid out or closed. On the trust prompt, Block skips the commands but applies the layout, whilst Cancel Setup keeps the worktree without commands or layout. A TUI started with --output-selection never focuses new workspaces. TUI warnings go to the debug log. πŸ’€

  • windows β€” list of tab definitions. Each tab has: name (supports env vars), command (empty uses default shell), cwd (default: $WORKTREE_PATH). If omitted, creates a single shell tab. The first tab uses the name and cwd of the first window.

For container execution, the following fields are available:

  • image β€” container image to use (required, e.g. "golang:1.22", "node:20")
  • runtime β€” container runtime binary (optional, auto-detected: podman preferred, then docker)
  • mounts β€” additional bind mounts (optional). Each mount has: source (host path), target (container path), read_only (default: false). The worktree path is automatically mounted to the working directory.
  • env β€” extra environment variables passed to the container (optional). Managed WORKTREE_*, REPO_*, and LAZYWORKTREE_* variables are forwarded automatically. 🧚
  • working_dir β€” working directory inside the container (default: /workspace)
  • extra_args β€” additional docker/podman run arguments (optional, e.g. --network=host, --privileged)
  • args β€” arguments passed after the image as CMD (optional). When command is set, it becomes the container entrypoint via --entrypoint, and args are passed as arguments. When command is empty, the image's default entrypoint is used with args appended. When neither command nor args is set, the container runs with its image defaults.
  • interactive β€” allocate TTY for interactive use (-it flags, default: false).

πŸ› οΈ custom_create_menus

Custom items for the worktree creation menu (triggered by the c key). The workflow, step by step: you first select a base branch, then the command runs to generate a branch name, and optionally a post-command runs after worktree creation. It's giving little assembly line, but cute. πŸŽ€

Each custom create menu entry supports the following fields:

  • label β€” display label shown in the menu (required)
  • description β€” help text shown next to the label (optional)
  • command β€” shell command that outputs a branch name on stdout (required)
  • interactive β€” run interactively (default: false). Set to true for TUI-based commands like fzf or jayrah. When true, lazyworktree suspends, the command runs in the terminal, and stdout is captured via a temp file. 🌸
  • post_command β€” a command that runs inside the shiny new worktree directory after it gets created (optional, bestie). It runs after the global/repo init_commands. It gets the full managed command environment: WORKTREE_*, REPO_*, and LAZYWORKTREE_* variables. ✨
  • post_interactive β€” run that post-command interactively (default: false). When it's true, the TUI suspends itself so you can actually see the command's I/O. 🫢

So like, by default commands run non-interactively with a 30-second timeout. When interactive: true, there's no timeout at all. The command output β€” first line only, whitespace trimmed, case preserved β€” becomes the suggested branch name. The pre-create command receives MAIN_WORKTREE_PATH, REPO_*, and empty managed WORKTREE_*/LAZYWORKTREE_* values, because duh, the new branch and worktree path don't exist yet πŸ’€. Post-commands are the moment for automated setup stuff β€” creating initial commits, installing dependencies, opening editors, the whole vibe. If a post-command fails, the error gets shown but the worktree is kept. This is literally the feature for gluing in external tools like JIRA, Linear, or your own custom scripts. πŸ”₯

Example configuration:

custom_create_menus:
  - label: "From JIRA ticket"
    description: "Create from JIRA issue"
    command: "jayrah browse 'SRVKP' --choose"
    interactive: true
    post_command: "git commit --allow-empty -m 'Initial commit for ${WORKTREE_BRANCH}'"
    post_interactive: false

🎨 custom_themes 🎨

Define custom themes that can inherit from built-in themes or from other custom themes. This is how you personalise the colour scheme without touching a single line of code, no cap. πŸ’…

Two ways to define a custom theme:

  1. Inherit from a built-in theme and override specific colours:
custom_themes:
  my-dark:
    base: dracula
    accent: "#FF6B9D"
    text_fg: "#E8E8E8"
  1. Define a complete theme with no base (all 11 colour fields required, bestie β€” all of them):
custom_themes:
  completely-custom:
    accent: "#00FF00"
    accent_fg: "#000000"
    accent_dim: "#2A2A2A"
    border: "#3A3A3A"
    border_dim: "#2A2A2A"
    muted_fg: "#888888"
    text_fg: "#FFFFFF"
    success_fg: "#00FF00"
    warn_fg: "#FFFF00"
    error_fg: "#FF0000"
    cyan: "#00FFFF"

Custom themes can also inherit from other custom themes, which is giving family tree energy 🌸. Available colour fields (snake_case in YAML): accent, accent_fg, accent_dim, border, border_dim, muted_fg, text_fg, success_fg, warn_fg, error_fg, cyan.

Colour values must be hex format (#RRGGBB or #RGB). When you're using a base theme, only specify the colours you wanna override. When there's no base, all 11 colour fields are required. From the theme selection screen, custom themes show up right alongside the built-in ones β€” iconic behaviour honestly. πŸ‘‘

🌈 Example Configuration 🌈

A practical config showing off the common settings, so you can steal it wholesale:

# UI and behaviour
theme: nord
icon_set: nerd-font-v3
sort_mode: switched
fuzzy_finder_input: true
auto_refresh: true
refresh_interval: 10
palette_mru: true
palette_mru_limit: 5

# Forge integration
issue_branch_name_template: "fix-{number}-{title}"
pr_branch_name_template: "pr-{number}-{generated}"

# Diff settings
git_pager: delta
max_diff_chars: 500000

# Security
trust_mode: tofu
merge_method: rebase

# Automation
init_commands:
  - link_topsymlinks
  - echo "Worktree ${WORKTREE_NAME} initialised"

# Custom commands
custom_commands:
  t:
    description: "Open tmux session"
    show_help: true
    tmux:
      session_name: "${REPO_NAME}_${WORKTREE_NAME}"
      attach: true
      on_exists: switch
      windows:
        - name: "editor"
          command: "nvim"
        - name: "shell"
          command: ""
  _review:
    command: "make review"
    description: "Review current worktree"

βœ… PREREQUISITES βœ…

  • Git 2.31+ (recommended, bestie, don't fight it)
  • Forge CLI: GitHub CLI (gh) or GitLab CLI (glab) for repository resolution and PR/MR status

🌱 OPTIONAL DEPENDENCIES 🌱

  • delta β€” for syntax-highlighted diffs ✨
  • lazygit β€” for full TUI git control
  • tmux β€” for tmux integration support
  • zellij β€” for zellij integration support
  • herdr β€” for Herdr integration support

πŸ“ EXAMPLES πŸ“

Open lazyworktree:

lazyworktree

Specify a custom worktree directory:

lazyworktree --worktree-dir ~/my-worktrees

Select a theme:

lazyworktree --theme nord

Use with the shell function for that "jump" functionality:

jt  # Opens TUI, select a worktree and press Enter to jump

Custom issue branch template in configuration:

issue_branch_name_template: "bug-{number}-{title}"

This creates branches like "bug-42-memory-leak" from issue #42. Obsessed. πŸ’–

🌍 ENVIRONMENT 🌍

These environment variables affect lazyworktree's behaviour when you use it with custom commands:

  • WORKTREE_BRANCH β€” name of the git branch
  • MAIN_WORKTREE_PATH β€” path to the main repository
  • WORKTREE_PATH β€” path to the selected worktree
  • WORKTREE_NAME β€” name of the worktree (the directory name)
  • REPO_NAME β€” name of the repository (from GitHub/GitLab)
  • REPO_OWNER β€” repository owner when available
  • REPO_REPONAME β€” repository name without the owner
  • LAZYWORKTREE_TYPE β€” source context type, such as pr, issue, or diff when known
  • LAZYWORKTREE_NUMBER β€” PR/MR or issue number when known
  • LAZYWORKTREE_TEMPLATE β€” branch-name template used during PR/MR or issue creation when known
  • LAZYWORKTREE_SUGGESTED_NAME β€” LazyWorktree's default branch/worktree name suggestion when known
  • LAZYWORKTREE_TITLE β€” PR/MR or issue title when known
  • LAZYWORKTREE_URL β€” PR/MR or issue URL when known
  • LAZYWORKTREE_DESCRIPTION β€” PR/MR or issue body when known
Heads up 🫢 β€” those contextual LAZYWORKTREE_* values are just empty when no PR/MR, issue, or diff source context is known. Not broken, just vibing empty.

πŸ“ CONFIGURATION FILES πŸ“

  • ~/.config/lazyworktree/config.yaml β€” the main configuration file
  • ~/.local/share/lazyworktree/trusted.json β€” Trust on First Use (TOFU) trusted file hashes
  • ~/.local/share/worktrees/<repo-name>/ β€” the default worktree storage location

πŸ‘€ SEE ALSO πŸ‘€

git-worktree(1), lazygit(1), tmux(1), zellij(1), herdr(1), delta(1)

πŸ’œ AUTHOR πŸ’œ

Chmouel Boudjnah <chmouel@chmouel.com>

Apache Licence 2.0

πŸ› REPORTING BUGS πŸ›

Report bugs at https://github.com/chmouel/lazyworktree/issues β€” like, please do, we wanna know 😭


And that's the whole page, bestie πŸŽ€ β€” this is lazyworktree, January 2026, page LAZYWORKTREE(1). Go forth and worktree slay πŸ’…βœ¨πŸ¦‹

πŸ“¦ where this page comes from