GitHub - 0dragosh/cwt: Rust TUI that manages git worktrees as first-class units of work for parallel Claude Code or Codex sessions, with tmux integration, automatic snapshots, and one-key PR shipping

cwt — Provider (Claude or Codex) Worktree Manager

A terminal UI for running parallel provider (Claude or Codex) sessions in isolated git worktrees. Built in Rust, it uses a local terminal multiplexer for interactive session management, preferring zellij when available and falling back to tmux.

The worktree is the first-class primitive — sessions attach to worktrees, not the other way around.

Worktree (unit of work)
  |-- Branch (auto-created or user-specified)
  |-- Session (provider instance, 0 or 1 active)
  |-- Lifecycle: ephemeral | permanent
  |-- State: idle | running | waiting | done | shipping

Why cwt?

When using a provider (Claude or Codex) on a real codebase, you often want to run multiple tasks in parallel — fix a bug, add a feature, write tests — without them stepping on each other. Git worktrees give you cheap, isolated copies of your repo. cwt manages the lifecycle of those worktrees and the provider sessions inside them, all from a single TUI.

  • Spin up a worktree in seconds — auto-named, auto-branched, ready to go
  • Never lose work — every deletion saves a .patch snapshot first
  • Stay organized — ephemeral worktrees auto-clean; permanent ones stick around
  • See everything at once — two-panel TUI with live session status, diff stats, and transcript previews
  • Scale up — dispatch tasks in bulk, import GitHub issues, broadcast prompts across sessions

Requirements

  • git (with worktree support)
  • zellij or tmux (interactive mode needs one local terminal multiplexer; zellij is preferred when both are installed)
  • A provider CLI (claude or codex)

Optional:

  • gh (GitHub CLI) — for PR creation and CI status
  • podman or docker — for per-worktree containers
  • ssh — for remote worktrees

Installation

Prebuilt binaries with cargo-binstall

cargo-binstall downloads the prebuilt release archive when one is available for your target. Install either zellij or tmux separately and make sure it is on your PATH.

Cargo (from crates.io)

cargo install does not install a terminal multiplexer for you. Install zellij or tmux separately and make sure it is on your PATH before running cwt.

Nix (recommended)

cwt provides a Nix flake with builds for Linux and macOS (x86_64 and aarch64). The Nix package includes tmux and git as runtime dependencies and wraps the binary so they are always on PATH. If zellij is also available in your environment, cwt will prefer it automatically for local interactive sessions.

# Run without installing
nix run github:0dragosh/cwt

# Install to your profile
nix profile install github:0dragosh/cwt

Add to a flake-based NixOS or home-manager configuration:

# flake.nix
{
  inputs.cwt.url = "github:0dragosh/cwt";

  # Option 1: use the overlay
  nixpkgs.overlays = [ cwt.overlays.default ];
  # then add pkgs.cwt to your packages

  # Option 2: reference the package directly
  environment.systemPackages = [ cwt.packages.${system}.default ];

  # Option 3: home-manager module (generates ~/.config/cwt/config.toml)
  imports = [ cwt.homeManagerModules.default ];
  programs.cwt = {
    enable = true;
    settings.session.default_permission = "elevated";
  };
}

See docs/nix.md for full home-manager module documentation.

From source

git clone https://github.com/0dragosh/cwt.git
cd cwt
cargo build --release
# Binary at target/release/cwt — add it to your PATH

Make sure git is on your PATH, and make sure zellij or tmux is installed and on your PATH. cwt cannot run its interactive workflows without one of them.

Quick Start

Interactive mode needs a local terminal multiplexer. If you launch cwt from a regular shell, it will bootstrap into a zellij session when zellij is installed, and otherwise fall back to tmux.

# 1. Navigate to any git repo
cd ~/my-project

# 2. Launch the TUI (cwt will bootstrap into zellij or tmux if needed)
cwt

# 3. Press 'n' to create a worktree (Enter for auto-generated name)
# 4. Press 's' to launch a provider session in it
# 5. Press 'Tab' to switch between the worktree list and inspector panels

Or use CLI commands directly:

cwt create my-feature --base main     # Create a worktree
cwt list                               # List all worktrees
cwt delete my-feature                  # Delete (saves a snapshot first)

# Dispatch parallel tasks — one worktree + session per task
cwt dispatch "implement auth" "add tests" "update docs"

# Import GitHub issues as worktrees
cwt import --github --limit 5

# Multi-repo mode
cwt add-repo ~/code/project-a
cwt add-repo ~/code/project-b
cwt forest                             # Launch forest TUI
cwt status                             # CLI summary across repos

Features

Worktree Management

  • Create with auto-generated slug names or explicit names, from any base branch
  • Two-tier lifecycle: ephemeral (auto-GC'd) and permanent (never auto-deleted)
  • Promote ephemeral worktrees to permanent with a single keypress
  • Snapshots: full diff saved as .patch before every deletion
  • Restore previously deleted worktrees from their snapshots
  • Garbage collection: prune old ephemeral worktrees, skipping those with running sessions, uncommitted changes, or unpushed commits
  • Setup scripts: automatically run a script (e.g., npm install) after worktree creation

TUI Interface

  • Two-panel layout: worktree list (grouped by lifecycle) + inspector (details, diff stat, session info)
  • Fuzzy filter: / to search/filter worktrees by name
  • Help overlay: ? for a full keybinding reference
  • Mouse support: click to select, scroll to navigate
  • Status bar: notification badges for waiting/done sessions

Terminal Multiplexer Support

Session Providers

  • cwt supports two provider options: claude and codex
  • You can set the default in config with session.provider = "claude" or "codex"
  • You can change the active provider at runtime by pressing o in the TUI
  • Press O to persist the currently selected provider as the default
  • Local interactive mode prefers zellij and falls back to tmux
  • Launching cwt outside a multiplexer auto-attaches to a cwt session in the preferred backend
  • In zellij, provider sessions and shells open in named tabs; in tmux, they open in panes/windows
  • Launch the provider (Claude or Codex) in the active multiplexer attached to any worktree
  • Resume previous sessions using the active provider's resume flow (--resume for Claude)
  • Focus an existing session tab/pane with a single keypress
  • Open shell in any worktree directory via the active multiplexer
  • Sessions survive TUI exit — closing cwt does not kill running sessions
  • Remote sessions still use tmux on the remote host today

Handoff

  • Bidirectional patch transfer between your main working directory and any worktree
  • Direction picker: worktree-to-local or local-to-worktree
  • Diff preview before applying
  • Gitignore gap warnings for untracked files that won't transfer

Hooks (Real-Time Provider Integration)

  • Unix domain socket listener for sub-second event delivery
  • Worktrees created by the provider (Claude or Codex) outside cwt appear in the list within one second
  • cwt hooks install patches .claude/settings.json and writes hook scripts to .cwt/hooks/

Forest Mode (Multi-Repo)

  • Register multiple repos with cwt add-repo <path>
  • Three-panel TUI: repos | worktrees | inspector
  • Aggregate session counts across all repos
  • cwt status for a one-line CLI summary

Agent Orchestration

  • Dispatch multiple tasks in parallel: cwt dispatch "task 1" "task 2" ...
  • Import issues from GitHub or Linear — creates worktrees and sessions per issue
  • Broadcast a prompt to all running sessions simultaneously

Ship Pipeline

  • Create PR from a worktree with auto-generated body from session transcript
  • CI status tracking: pass/fail/pending via gh run list
  • Ship it: one-keypress macro to push, create PR, and mark as shipping

Per-Worktree Containers

  • Podman or Docker support (prefers Podman for rootless compatibility)
  • Auto-detect Containerfile, Dockerfile, or .devcontainer/devcontainer.json
  • Port management: auto-assign non-conflicting ports per worktree

Permission Levels

cwt supports three permission tiers for provider sessions (Claude/Codex), giving you fine-grained control over how much autonomy the provider gets:

Level Badge Behavior
Normal N (gray) Plain provider command — asks for permission on each tool use (default)
Elevated E (yellow) Injects sandbox settings into .claude/settings.local.json — the provider runs autonomously within a sandbox
Elevated Unsandboxed U! (red) Appends --dangerously-skip-permissions — full autonomy, no sandbox
  • Press m to cycle through modes at runtime
  • Press M to save the current mode as the default in your config
  • The active level is shown as a badge in the top bar
  • Each worktree gets its own .claude/settings.local.json, so there are no concurrency conflicts between sessions

Provider-specific mode behavior:

  • Claude: Elevated injects sandbox settings; Unsandboxed uses --dangerously-skip-permissions.
  • Codex: Unsandboxed uses --full-auto; Elevated Unsandboxed uses --dangerously-bypass-approvals-and-sandbox.

The elevated (sandboxed) provider mode writes these settings before launch:

{
  "permissions": { "allow": [], "deny": [] },
  "sandbox": {
    "enabled": true,
    "autoAllowBashIfSandboxed": true,
    "allowUnsandboxedCommands": false
  }
}

Remote Worktrees

  • SSH-based remote host management
  • Create and manage worktrees on remote machines
  • Cross-machine handoff via patches
  • Latency-aware polling with network status indicators

Keybindings

Worktree Actions

Key Action Context
n New worktree Global
s Launch/resume provider session Worktree selected
h Handoff changes (worktree <-> local) Worktree selected
p Promote to permanent Ephemeral selected
d Delete (with snapshot) Worktree selected
g Run garbage collection Global
r Restore from snapshot Global
Enter Launch/resume provider session Worktree selected
e Open shell in worktree Worktree selected

Orchestration

Key Action Context
t Dispatch tasks (multi-worktree) Global
b Broadcast prompt to all sessions Global

Ship Pipeline

Key Action Context
P Create PR (push + gh pr create) Worktree selected
S Ship it (push + PR + mark shipping) Worktree selected
c Open CI logs in browser Worktree selected

Permissions

Key Action Context
m Cycle mode (Normal/Unsandboxed/Elevated Unsandboxed) Global
M Save current mode as default Global
o Cycle session provider (Claude/Codex) at runtime Global
O Save current provider as default Global

Navigation

Key Action Context
j / Down Move down / scroll inspector Global
k / Up Move up / scroll inspector Global
Tab Switch panel focus (forward) Global
Shift+Tab Switch panel focus (back) Global
R Switch to repo panel Forest mode
/ Filter/search worktrees Worktree list
Esc Clear filter / close dialog Global
? Toggle help overlay Global
q Quit Global
Ctrl+C Force quit Global

CLI Commands

Command Description
cwt Launch the TUI (default)
cwt tui Launch the TUI (explicit)
cwt list List all managed worktrees
cwt create [name] --base <branch> Create a new worktree
cwt create [name] --remote <host> Create on a remote host
cwt delete <name> Delete a worktree (saves snapshot)
cwt promote <name> Promote ephemeral to permanent
cwt gc [--execute] Preview/run garbage collection
cwt hooks install Install provider hook scripts
cwt hooks uninstall Remove hook scripts
cwt hooks status Show hook and socket status
cwt dispatch "task" ... Dispatch parallel tasks
cwt import --github [--limit N] Import GitHub issues as worktrees
cwt import --linear [--limit N] Import Linear issues as worktrees
cwt prompt Print active cwt worktree name
cwt add-repo <path> Register a repo for forest mode
cwt forest Launch forest (multi-repo) TUI
cwt status Summary of all repos and sessions

Starship prompt integration

cwt prompt prints the current managed worktree name when your shell is inside that worktree directory, and prints nothing otherwise. This makes it easy to surface worktree context in starship (similar to worktrunk):

[custom.cwt]
command = "cwt prompt"
when = "cwt prompt | grep -q ."
format = "[$output]($style) "
style = "bold purple"

Configuration

cwt reads configuration from .cwt/config.toml (per-project) and ~/.config/cwt/config.toml (global). Forest mode uses ~/.config/cwt/forest.toml.

[worktree]
dir = ".claude/worktrees"        # worktree root (relative to repo root)
max_ephemeral = 15               # GC threshold
auto_name = true                 # generate slug names when no name given

[setup]
script = ""                      # path to setup script (relative to repo root)
timeout_secs = 120               # setup script timeout

[session]
auto_launch = true               # launch session provider on worktree create
provider = "claude"              # "claude" | "codex"
command = ""                     # optional command override (defaults to provider binary)
provider_args = []               # extra args for provider invocation
default_permission = "normal"    # "normal", "elevated", or "elevated_unsandboxed"

# Permission-level overrides (optional — sensible defaults built in)
# [session.permissions.normal]
# extra_args = []
#
# [session.permissions.elevated]
# extra_args = []
# [session.permissions.elevated.settings_override.sandbox]
# enabled = true
# autoAllowBashIfSandboxed = true
#
# [session.permissions.elevated_unsandboxed]
# extra_args = ["--dangerously-skip-permissions"]

[handoff]
method = "patch"                 # "patch" or "cherry-pick"
warn_gitignore = true            # warn about .gitignore gaps

[ui]
theme = "default"                # color theme
show_diff_stat = true            # show file change counts in list

[container]
enabled = false                  # enable container support
runtime = "auto"                 # "podman", "docker", or "auto"
auto_ports = true                # auto-assign ports per worktree

# Remote hosts (one [[remote]] block per host)
[[remote]]
name = "build-server"
host = "build.example.com"
user = "dev"
worktree_dir = "/data/worktrees"

Architecture

src/
  main.rs          # CLI parsing, TUI bootstrap, startup checks
  app.rs           # App state, event loop, keybinding dispatch, rendering
  config/          # TOML config loading (project + global fallback)
  state/           # JSON state persistence (.cwt/state.json)
  git/             # Git worktree, branch, and diff operations
  worktree/        # Worktree CRUD, handoff, snapshots, setup, slug generation
  session/         # provider session launcher, tracker, transcript parser
  tmux/            # local multiplexer abstraction (zellij + tmux)
  hooks/           # Unix socket listener, hook events, script installer
  forest/          # Multi-repo config, global index
  orchestration/   # Task dispatch, issue import, broadcast, dashboard
  ship/            # PR creation, CI status, ship pipeline
  env/             # Containers (Podman/Docker), devcontainer, ports, resources
  remote/          # SSH host management, remote sessions, cross-machine sync
  ui/              # ratatui widgets: layout, list, inspector, dialogs, theme

Troubleshooting

cwt says "zellij or tmux is required" Install either zellij or tmux first, then run cwt again. When both are installed locally, cwt prefers zellij; otherwise it falls back to tmux.

Worktrees don't appear after a provider creates them Run cwt hooks install to set up the real-time hook integration. Without hooks, cwt discovers worktrees on periodic refresh (every few seconds).

gh commands fail (PR creation, CI status) Make sure the GitHub CLI is installed and authenticated (gh auth login).

Sessions show "idle" even though the provider is running cwt detects session status by parsing ~/.claude/projects/ transcripts. If the path hash doesn't match, status won't update. Restarting cwt re-scans the project directory.

GC skipped a worktree I expected it to prune GC never prunes worktrees with running sessions, uncommitted changes, or unpushed commits. Check cwt list for details.

Contributing

See CONTRIBUTING.md for development setup, code conventions, and how to submit changes.

Releases

Releases are managed automatically by release-plz. Pushing conventional commits (fix:, feat:, etc.) to main triggers a release PR with version bump, lockfile update, and changelog. Merging the PR publishes to crates.io and creates a GitHub Release.

License

MIT