hz is a fast terminal workflow for parallel development with agents. It
creates isolated Git workspaces, makes it fast to move between them, hands off
diffs safely, and runs repo-local lifecycle hooks.
hz is pre-1.0. The current release is usable for local Git worktree, handoff,
lifecycle, shell integration, machine-readable JSON output, and install/update
workflows. Prefer --machine or --json for scripts, and expect command shapes
to keep tightening before 1.0.
- Creates isolated Git worktrees for parallel human or AI-agent tasks.
- Forks the current worktree state into a detached scratch worktree.
- Installs shell integration so
hz worktree new,hz worktree fork,hz worktree cd, andhz worktree handoffcan change your current directory. - Applies uncommitted changes between linked worktrees without forcing a commit.
- Moves branch ownership between worktrees when both sides are clean.
- Runs repo-local setup and cleanup hooks on explicit opt-in create/remove flags for reproducible agent workspaces.
- Exposes JSON and
--machinemodes for agents and automation. - Lists, finds, prunes, and removes managed and unmanaged worktrees.
- Pins managed worktrees so auto-prune will not remove them.
- Installs and updates release binaries from GitHub releases.
# one-time repo setup
hz init
git add .hz
git commit -m "Add hz lifecycle config"
hz install zsh # or bash/fish; restart or source your shell rc file
# create an isolated workspace for a task/agent and cd into it
hz worktree new fix-login
# run your terminal AI agent or manual workflow here
# ...edit files...
# hand the diff back to the linked local worktree and return there
hz worktree handoff
# review with dx if desired, then clean up
dx
hz worktree rm -f fix-login # force is needed if the source worktree still has handed-off changesWithout shell integration, use hz worktree path <target> to print a worktree path and
cd "$(hz worktree path <target>)" from your shell.
For agents and scripts, prefer the machine-readable surface:
hz --machine worktree new fix-login --repo .
hz --machine worktree list --repo .
hz --machine worktree handoff fix-login --repo .
hz --machine worktree remove fix-login --repo . --forcehz init writes repo-local lifecycle files under .hz/. Commit them before
starting the task flow above so the destination worktree is clean when
hz worktree handoff applies changes back to it. For a throwaway first demo where you do
not need lifecycle hooks, skip hz init.
Install the latest release with the shell installer:
curl -fsSL https://raw.githubusercontent.com/phongndo/hz/main/scripts/install.sh | shThe curl installer is the only supported install path for now. Homebrew, mise, Cargo, and other package-manager installs are deprecated; reinstall with the command above if you used one of those paths before.
The installer downloads the matching GitHub release archive for macOS or Linux,
verifies its SHA-256 file with shasum or sha256sum, and installs hz to
~/.local/bin by default. Set HZ_ALLOW_UNVERIFIED=1 only when you explicitly
want to install without checksum verification.
curl -fsSL https://raw.githubusercontent.com/phongndo/hz/main/scripts/install.sh | HZ_VERSION=0.6.0 sh
curl -fsSL https://raw.githubusercontent.com/phongndo/hz/main/scripts/install.sh | HZ_INSTALL_DIR=/usr/local/bin shVerify the installed binary:
which -a hz
hz --versionThe default curl install path is ~/.local/bin/hz; if you set
HZ_INSTALL_DIR, the first hz on PATH should come from that directory.
Update a curl-installed binary in place:
hz update
hz update --target-version 0.6.0After installing the binary, install shell integration for auto-cd and completions:
hz install zsh
source ~/.zshrchz worktree new creates a scratch Git worktree with a human-facing handle and a UUID
directory under ~/.hz/worktrees/<repo>/ by default. Pass a name or --branch
to create a branch-backed worktree:
hz worktree new fix-login
hz worktree fork
hz worktree new
hz worktree ls
hz worktree pwd
hz worktree path fix-login
hz worktree cd fix-login
hz worktree cd
hz worktree rm fix-loginUse hz wt ... as a shorter alias for hz worktree ....
hz worktree new without a name generates a four-character lowercase alphanumeric handle
and leaves the worktree on a detached HEAD. Managed worktrees are registered
in ~/.hz/registry.json.
hz worktree pwd prints the current worktree target (local, a branch name, or a
detached handle). Pass --json to include the target, repo, and path.
hz worktree fork creates a new detached worktree at the current HEAD and applies the
current worktree diff there, including untracked files. The source worktree is
left unchanged. Pass an optional handle to name the detached fork, or
--no-diff to fork only the current HEAD without copying local changes:
hz worktree fork
hz worktree fork alt-try
hz worktree fork --no-diffhz worktree ls, hz worktree cd, and hz worktree rm also detect unmanaged Git worktrees created by
other tools. Removing an unmanaged worktree outside ~/.hz/worktrees/<repo>/
asks for confirmation because the path is not in hz's worktree namespace. Add
[worktree].user_managed_roots in .hz/hz.toml for other directories that
should be treated as user-managed by hz.
Detached scratch worktrees are capped at 10 by default. Creating another
detached worktree auto-removes the oldest clean managed detached worktrees until
the cap is satisfied. Pinned, branch-backed, unmanaged, dirty, unknown, and
current worktrees are not auto-removed. If there are not enough removable
worktrees, hz worktree new and hz worktree handoff --new refuse to create another detached
worktree. Set [worktree].max_detached in .hz/hz.toml, or pass
--max-detached <count> to hz worktree new or hz worktree handoff --new; 0 disables
auto-pruning.
Branch-backed worktrees are also capped at 10 by default. Creating another
branch-backed worktree auto-removes the oldest clean managed branch-backed
worktrees until the cap is satisfied. Removing a branch-backed worktree removes
only the checkout; the Git branch remains in the repo and can be checked out
again later. Pinned, detached, unmanaged, dirty, unknown, and current
worktrees are not auto-removed. Set [worktree].max_branch_worktrees in
.hz/hz.toml, or pass --max-branch-worktrees <count> to hz worktree new or
branch-backed hz worktree handoff --new; 0 disables auto-pruning.
Use hz worktree pin <target...> to keep managed worktrees out of auto-prune, and
hz worktree unpin <target...> to make them eligible again. hz worktree ls --pinned shows only
pinned worktrees; hz worktree ls --unpinned shows only unpinned worktrees. Pinned
worktrees can still be removed explicitly with hz worktree rm.
Auto-pruning is enabled by default. Set [worktree].auto_prune = false to opt
into keeping managed worktrees instead of deleting them at the configured
limits. Dirty and current worktrees are still protected from auto-removal.
Repo config can set the default base branch for new worktrees and additional user-managed worktree roots:
# .hz/hz.toml
[worktree]
auto_prune = true
max_detached = 10
max_branch_worktrees = 10
default_base = "dev"
user_managed_roots = ["~/.codex/worktrees"]With that config, hz worktree new feature/ui behaves like
hz worktree new feature/ui --base dev. Passing --base main still wins for deliberate
main-based work.
Ignored local files are not present in a fresh Git worktree. To copy selected
ignored setup files into new managed hz worktrees, add a root
.worktreeinclude file using Gitignore-style patterns:
# .worktreeinclude
.env
.env.local
config/secrets.json
hz copies only ignored files that match .worktreeinclude; tracked files and
other untracked files are left alone. Source symlinks are skipped, and existing
destination files are never overwritten.
hz worktree cd prints a path for scripts. To make hz worktree new and hz worktree cd change the
current shell directory, run hz install <shell> once to update your shell rc
file:
hz install zsh
hz install bash
hz install fishFor zsh, this updates ~/.zshrc. Restart your shell or run source ~/.zshrc
after install. With the integration loaded, plain hz worktree new ... or hz worktree fork ...
creates the worktree and changes into it, and hz worktree cd returns to the local repo
root.
--json, --path-only, and help calls still pass through to the real binary
without changing directories.
The shell integration also installs completions for zsh, bash, and fish.
Completions include command aliases such as hz worktree cd, hz worktree ls, and hz worktree rm,
nested hz worktree ... commands, command flags such as --machine, shell
names for hz init/hz shell, and live worktree targets for commands that
accept them.
By default, commands are human-facing. They keep terminal output
readable and, when shell integration is installed, commands such as hz worktree new,
hz worktree fork, hz worktree cd, and hz worktree handoff can change the current shell directory.
Pass --json to data-producing commands when another process needs parseable
stdout. Pass --machine to force JSON, bypass shell auto-cd, and refuse unsafe
prompts in non-interactive paths:
hz worktree pwd --json
hz worktree path fix-login --json
hz --machine worktree listFor compatibility, hz agent ... remains as a machine-readable alias for the
same worktree commands.
hz worktree handoff applies the current worktree's uncommitted diff to its linked
counterpart by default. From a linked worktree it applies the patch to local.
From local, pass a worktree handle such as hz worktree handoff fix-login to apply the
local patch there. After a patch handoff, running hz worktree handoff from local
defaults back to the last linked worktree. The destination must be clean unless
its current diff still matches the last patch handed off between that pair; in
that case hz safely replaces it with the source diff. Source changes are left
in place. With shell integration loaded, successful handoffs change into the
destination worktree unless --json, --machine, --path-only, or help is
passed.
Use hz worktree handoff --new to create a new detached destination worktree and apply
the current patch there. Use hz worktree handoff --new fix-login to create a
branch-backed destination worktree named fix-login.
Use hz worktree handoff <worktree> --branch to move branch ownership instead of
applying a patch. Branch handoff is clean-only on both sides.
Diff review now lives in the separate dx CLI.
Use dx from any hz worktree when you want the terminal review UI or plain
diff output:
dx
dx --staged
dx --base main
dx --pr 123
dx --patch changes.diffhz init initializes repo-local lifecycle config:
.hz/hz.toml
.hz/environment/setup
.hz/environment/cleanup
.hz/hz.toml declares the commands hz should run:
[worktree]
max_detached = 10
max_branch_worktrees = 10
# user_managed_roots = ["~/.codex/worktrees"]
[lifecycle]
setup = [".hz/environment/setup"]
cleanup = [".hz/environment/cleanup"].hz/environment/setup and .hz/environment/cleanup are executable script
files. Edit them to contain the repo setup and cleanup commands an agent
worktree should run. hz worktree new --setup runs the configured setup command after
creating a worktree, and hz worktree rm --cleanup runs the configured cleanup command
before removing one. Use --no-setup or --no-cleanup to explicitly suppress a
hook when using aliases or wrapper scripts. Hook stdout is forwarded to stderr
so --json and --path-only output stays machine-readable.
Lifecycle config is read from the target worktree. Commit .hz/hz.toml and any
referenced scripts before opting in to hz worktree new --setup for newly created
worktrees.
hz worktree ls display is configurable from .hz/hz.toml:
[list]
headers = "auto" # auto | always | never
columns = ["marker", "target", "status", "modified", "path"]
compact_columns = ["marker", "target", "status"]
[color]
mode = "auto" # auto | always | never
scheme = "terminal"Columns can include marker, target, branch, handle, status, base,
modified, and path. Color defaults to terminal-native ANSI colors so the
user's terminal color scheme decides the actual palette. Custom hz worktree ls color
schemes can opt into different ANSI color names while still letting the terminal
theme provide the actual colors.
See docs/config.md for the full config reference.
For compatibility, hz init <shell> still installs shell integration, but
hz install <shell> is the documented command for shell setup.
See docs/cli.md for the full human and machine command reference, common options, and scripting notes.
crates/hz-cli command parsing, terminal output, install/update, and CLI UX
crates/hz-bench headless command benchmark utilities
crates/hz-command command facade shared by CLI and future runtime callers
crates/hz-core shared errors and common models
crates/hz-git low-level Git integration boundary
crates/hz-worktree worktree domain boundary: new, path, list, handoff, remove
The command crate is the main extension seam. CLI and future runtime code should
call hz-command instead of duplicating workflow logic. Provider or agent-runtime
integrations can sit beside these crates without forcing the existing command
surface to depend on plugin machinery.
Install the repo Rust toolchain:
rustup showOr enter the Nix development shell:
nix developRun local checks:
just setup
just hooks # validate hk.pkl config
just hk-check # run git pre-commit checks via hk
just check
just test
just build
just smokeFor contribution guidelines, dev-shell details, and CI expectations, see CONTRIBUTING.md.
.github/workflows/quality.yml runs rust-analyzer diagnostics, formatter,
Clippy, workspace tests, a full workspace build, a headless hz-bench smoke,
and installer/update smoke tests against a local release fixture.
.github/workflows/release.yml builds release archives with SHA-256 checksum
files for supported macOS and Linux targets when a v*.*.* tag is pushed.
MIT. See LICENSE.