treehouse

CI Release Platform X Discord

Manage worktrees without managing worktrees.

Are you still only working on one task at a time? Are you manually juggling between a few clones of the same repo? Or... are you starting a new worktree for every agent session, losing all your installed dependencies and build cache each time, and wondering why your agents are slow?

treehouse demo

Treehouse helps you manage a pool of reusable, isolated worktrees so each of your agents gets its own environment instantly — no cloning, no conflicts, no coordination overhead. - **Instant isolation** — `treehouse` puts you into a clean worktree with zero hassel. - **Reusable worktrees** — worktrees are preserved in a pool when you're done, with dependencies and build cache intact, ready for the next agent. - **Conflict-free** — automatic detection of in-use worktrees and your agents never step on each other's toes. ## Quick Start ```sh $ cd myproject # start in your repo as usual $ treehouse # get a worktree and drop into a subshell 🌳 Entered worktree at ~/.treehouse/myproject-a1b2c3/1/myproject. Type 'exit' to return. # You're now in an isolated worktree. # Run your AI agent, make changes, do whatever you need. $ exit # exit the subshell when you're done 🌳 Terminated lingering processes: opencode (pid 12345) 🌳 Worktree returned to pool. ``` ## Install **macOS / Linux** ```sh curl -fsSL https://kunchenguid.github.io/treehouse/install.sh | sh ``` **Windows (PowerShell)** ```powershell irm https://kunchenguid.github.io/treehouse/install.ps1 | iex ``` **Nix** ```sh nix run github:kunchenguid/treehouse ``` Or add to your flake inputs: ```nix treehouse = { url = "github:kunchenguid/treehouse"; inputs.nixpkgs.follows = "nixpkgs"; }; ``` **Go** ```sh go install github.com/kunchenguid/treehouse@latest ``` **From source** ```sh git clone https://github.com/kunchenguid/treehouse.git cd treehouse make install ``` ## How It Works Treehouse manages a **pool of git worktrees** per repository, stored under the configured treehouse root. The default treehouse root is `~/.treehouse/`. ``` treehouse │ ▼ Find repo root │ ▼ git fetch origin │ ▼ ┌───────────────────────────────────────┐ │ Scan pool for available worktree │ │ (not leased, not in-use, not dirty) │ └──────────┬────────────────────────────┘ │ ┌────┴────┐ │ Found? │ └────┬────┘ yes/ \no / \ ▼ ▼ Reset to Create new worktree latest (detached HEAD at default latest default branch branch) & add to pool \ / \ / ▼ Spawn subshell in worktree (agent works here) │ ▼ exit subshell │ ▼ Terminate lingering worktree processes, reset worktree, & return to pool (ready for next agent) ``` - **Detached HEAD** — worktrees use detached HEAD mode, reset to whichever of the local or remote default branch is further ahead, avoiding branch name conflicts entirely. - **No daemon** — all operations are inline CLI commands. No background processes, no state to get corrupted. - **In-use detection** — treehouse scans running processes and short-lived owner reservations to determine which worktrees are in-use. Reservations are persisted only while `get`, `destroy`, and `prune` lifecycle work is running. - **Durable leases** — `treehouse get --lease` reserves a worktree as a persistent home without keeping a process inside it. The lease is recorded in treehouse's own state, so the worktree is never handed out by a later `get` and never removed by `prune` until you release it with `treehouse return`. Unlike process-based in-use detection, a lease survives with zero processes running inside the worktree. - **Dirty detection** - treehouse treats tracked changes and untracked files as dirty, even when repository config hides untracked files from normal `git status` output. - **Safe pruning** - By default, `treehouse prune` removes only idle managed worktrees whose HEAD is already merged into the default branch and whose working tree is clean. `treehouse prune --all` applies the same safety checks across every managed pool under the user-level treehouse root. Backing-repository-missing orphans are reported by default; `--prune-orphans` includes them as unverified prune candidates, and `--yes` is required before deletion. It is a dry run unless you pass `--yes`. ## CLI Reference | Command | Description | | -------------------------- | ---------------------------------------------------- | | `treehouse` | Get a worktree and open a subshell (alias for `get`) | | `treehouse get` | Acquire a worktree from the pool | | `treehouse get --lease` | Durably lease a worktree without a subshell; print its path | | `treehouse status` | Show pool status (highlights leased and current worktrees) | | `treehouse return [path]` | Release any lease, terminate lingering worktree processes, and return it to the pool | | `treehouse prune` | Dry-run removal of stale idle worktrees in the current repo pool | | `treehouse prune --all` | Dry-run removal of stale idle worktrees across every managed pool | | `treehouse destroy ` | Dry-run removal of one worktree (safe by default; `--yes` to execute) | | `treehouse destroy --all` | Dry-run removal of every disposable worktree in that pool | | `treehouse init` | Create a default `treehouse.toml` config file | | `treehouse update` | Update treehouse to the latest version | ### Flags | Command | Flag | Description | | --------- | --------- | --------------------------------- | | `get` | `--lease` | Durably lease the worktree without opening a subshell; print only its path to stdout | | `get` | `--lease-holder` | Optional label recorded as the lease holder (defaults to `$TREEHOUSE_LEASE_HOLDER`) | | `return` | `--force` | Clean, reset, and return without prompting | | `prune` | `--yes` | Delete listed prune candidates instead of doing a dry run | | `prune` | `--all` | Sweep every managed pool under the user-level treehouse root | | `prune` | `--global` | Alias for `--all` | | `prune` | `--prune-orphans` | Include backing-repository-missing orphans in prune candidates | | `prune` | `--verbose`, `-v` | Show detailed skip diagnostics | | `destroy` | `--all` | Remove all worktrees in the named pool (requires a pool path) | | `destroy` | `--yes` | Execute the removal instead of doing a dry run | | `destroy` | `--include-unlanded` | Also remove dirty, unmerged, or unverified worktrees (irreversible data loss) | | `destroy` | `--include-in-use` | Also remove worktrees with a running process or owner reservation (processes are terminated cleanly first) | | `destroy` | `--include-leased` | Also remove a leased worktree; only when the exact path is named, never via `--all` | ### Leasing a worktree (no subshell) `treehouse get` normally opens an interactive subshell whose lifetime is the hold: when the shell exits, the worktree returns to the pool. That is awkward for callers that need a worktree to persist as a permanent home with no long-lived process inside it. `treehouse get --lease` is the non-interactive, durable alternative: ```sh path=$(treehouse get --lease) # $path is the leased worktree's absolute path; all banners went to stderr. ``` It acquires a worktree exactly like `get`, but instead of opening a subshell it marks the worktree **leased** in treehouse's persistent state and prints only the worktree's absolute path to stdout (every human-facing message goes to stderr, so command substitution stays clean). A leased worktree is never handed out by a later `get` and never removed by `prune`, regardless of whether any process runs inside it, until the lease is explicitly released. A bulk `treehouse destroy --all` never removes it either; only naming its exact path with `treehouse destroy --include-leased --yes` will. Pass `--lease-holder