worktree-env (wte)
Automatically prepare an isolated, runnable local development environment for every Git worktree. Lightweight, minimal, no magic.
The problem
Git worktrees are widely used for parallel development and task isolation. However, a new worktree usually contains only code, not a development environment that is ready to run:
- The frontend, backend, database, and debugger still use the same fixed ports, preventing multiple worktrees from running at the same time.
.envfiles, private keys, and other local secrets must be copied repeatedly and can easily be committed by mistake.- URLs and port settings shared by services within a project must be kept in sync manually.
Common solutions often require adding extra scripts to the project, changing how it
is started, modifying AGENTS.md or CLAUDE.md, or creating skills.
These approaches are intrusive to some degree: they must either be adopted across
the team or affect how other team members work.
wte uses a Git post-checkout hook to assign stable, conflict-free ports to a
project's worktrees, link environment variables, and generate local configuration.
The hook is not committed to the repository. It is available globally on the local
machine and does not modify any project code.
Comparison with existing tools
- Portless: Requires applications to be
started through
portless, introducing a reverse proxy, a local CA, and a background service. - devports: Wraps worktree creation and
removal in
devportscommands; worktrees created directly by an agent or IDE are not handled automatically. - Worktrunk: Replaces the native Git workflow with
wt, does not participate in environment setup, and does not automatically handle worktrees created directly by an agent or IDE. - workz: Uses
.workz.toml,workz sync/workz start, or separately configured hooks for Cursor, Claude Code, and Worktrunk. - Hyve: Adopts a
hyve create/hyve runworkflow and depends on Docker, database containers, and service orchestration.
wte does not take over how worktrees are created or how a project is started.
Instead, it automatically projects a complete local development environment after a
worktree is created. It requires no changes to project code, start commands, or agent
prompts; no scripts need to be added to the project, and no traffic proxy or resident
process is required. Worktrees created by Git, an IDE, or a coding agent can all be
handled automatically.
All rules are declared explicitly in profiles stored outside the repository. For each
worktree, wte assigns stable ports, mounts secrets, generates local configuration,
and can initialize dependencies in the background, making the worktree ready
immediately after creation.
Features
- Allocates contiguous port blocks from a machine-wide shared pool and keeps them stable for the lifetime of the worktree path.
- Mounts secrets stored outside the repository into the worktree as symlinks, preserving a single source of truth and avoiding copy and paste.
- Organizes configuration by repository rather than by service. Supports monorepos and multiple port requests.
- Optionally monitors host directories to discover newly added linked worktrees, including those created within coding agent sandboxes.
- Preserves the project's own Git hooks. After the
post-checkouthook forwtefinishes, it invokes the project's own executable Git hook. - Native Git hooks handle day-to-day synchronization without a wrapper or an agent skill, and without changing coding agent prompts or project start commands.
Requirements
- macOS or Linux
- Python 3.9+
- Git
- Bash
Installation
uv tool install worktree-env
Upgrading
uv tool upgrade worktree-env
Optional setup skill
The wte-setup skill helps an agent inspect your project, draft a profile, migrate local configuration outside the repository, and verify the result. It distinguishes one-time migration from ongoing worktree projection and requests approval for planned changes. The skill is optional; normal synchronization runs through Git hooks and the optional Monitor.
Get the skill from this repository (use your existing checkout if you have one):
git clone --depth 1 https://github.com/archcst/worktree-env.git
cd worktree-env
Review skills/wte-setup/SKILL.md and its references/ directory, then run this
from the repository root to install the complete skill at user scope:
(
set -eu
dest="$HOME/.agents/skills/wte-setup"
if [ -e "$dest" ] || [ -L "$dest" ]; then
printf 'Already installed: %s; back it up and move it aside before installing.\n' "$dest" >&2
exit 1
fi
mkdir -p "$(dirname "$dest")"
cp -R skills/wte-setup "$dest"
)
Pi discovers ~/.agents/skills/ automatically. Open a new agent session in the
project you want to configure and invoke:
/skill:wte-setup Help me configure worktree-env for this project.
For other agent tools, use their documented user-level skill directory and invocation
method. Copy the entire wte-setup/ directory, including its references. Installing
the skill only copies instructions; it does not run wte init or change project
configuration. During use, the agent can guide you through installing worktree-env.
To update, pull the latest repository changes, review the skill, and move the existing installed directory to a backup outside all skill discovery directories before rerunning the install block. Keep any personal edits in that backup. To remove the skill, remove only its installed directory; wte profiles, hooks, and Monitor remain unchanged.
Getting started
wte init
This command:
- Initializes the personal configuration directory at
~/.config/wte/. - Runs
git config --global core.hooksPath ~/.config/wte/hooksto install the global Git hook dispatcher.
If the global
core.hooksPathalready points somewhere else,wtedisplays the current value and refuses to change it. Confirm its purpose and migrate or remove it as appropriate before retryingwte init.
~/.config/wte/
Personal profiles, hooks, and runtime state are stored together in:
~/.config/wte/
├── config.yaml # Machine-wide port pool
├── project_a.yaml # Project A profile
├── project_b.yaml # Project B profile
├── hooks/ # Global Git hook dispatcher
└── state/ # Managed by wte; do not edit manually.
├── ports.json # Port registry
├── ports.lock # Concurrency lock
├── hooks-state.json # Hook installation state
└── reconciler.log # Monitor log (present only when the optional Monitor is enabled; see the Monitor section)
Every root-level *.yaml file except config.yaml is loaded as a project profile.
Set WTE_CONFIG_HOME to change this directory. When it is not set,
XDG_CONFIG_HOME is respected.
Configuration examples
Port range
The machine-wide port range is configured in ~/.config/wte/config.yaml:
port-range:
start: 20000
end: 29999
The default range is 20000-29999. You can change it manually; new worktrees will
be allocated from the new range, while existing allocations remain unchanged.
Project profile
Copy the configuration template:
cp ~/.config/wte/project.example.yaml.template \
~/.config/wte/example-project.yaml
The following profile describes a project with separate frontend and backend services:
name: example-project
match:
# Points to the project's main worktree directory.
main-worktree: $HOME/code/example-app
port-claims:
# Names of the ports to request. Add as many as the project needs;
# each id must be unique within the profile.
- id: frontend_port
- id: backend_port
link-files:
# Files or directories shared through symlinks.
# source points to an existing file or directory, while target is a path
# relative to the worktree root.
- source: $HOME/path/to/your/frontend.env
target: frontend-dir/.env
- source: $HOME/path/to/your/backend.env
target: backend-dir/.env
- source: $HOME/path/to/your/certs
target: backend-dir/certs
write-files:
# Frontend configuration:
- target: frontend-dir/.env.development
body: |
VITE_PORT=${frontend_port}
SERVER_URL=http://127.0.0.1:${backend_port}
# Backend configuration:
- target: backend-dir/.env.development
body: |
PORT=${backend_port}
link-files shares the source directly: edits through any worktree affect all
worktrees using it. Existing real directories at a target are rejected, not
removed or merged; existing files and symlinks may be replaced. Link and write
targets must not be equal or nested inside one another. Sources must exist as
files or directories; missing or unsupported sources are skipped with a warning.
This example applies when both the frontend and backend can load
.env.{env name}files. Adjust it to match how your project loads environment variables.After a worktree directory is deleted, its ports are reclaimed the next time
wteis triggered.
Monitor
Some coding agents create worktrees inside a sandbox, which can prevent the wte hook from running.
To support these tools, enable the Monitor:
wte monitor enable
It watches the profile directory and the .git/worktrees/ metadata directory
associated with each configured main worktree:
- macOS uses LaunchAgents with
WatchPaths. - Linux uses systemd user path units.
When a watched directory changes, the operating system starts a short-lived Reconciler. It is not a resident daemon and does not poll on a timer, so its resource usage is minimal.
The Reconciler:
- Runs
git worktree list --porcelainto retrieve the actual list of worktrees. - Compares it with
ports.json, then assigns ports, mounts secrets, generates files, and starts configured initializers for unregistered worktrees.
When profiles are added, removed, or pointed to a different main-worktree, the
Monitor automatically refreshes its repository watch paths. After upgrading an
existing installation to this version, run wte monitor enable once to activate
automatic profile monitoring; subsequent profile changes require no manual refresh.
To disable the Monitor, run:
wte monitor disable
Afterward, filesystem changes will no longer be monitored.
Asynchronous initialization
wte can automatically run commands after a worktree is created, allowing the
environment to be initialized in the background:
setup-commands:
- command: [npm]
args: [install]
cwd: frontend-dir # Use "." to run from the worktree root.
skip-if: node_modules # Skip this command if the file or directory exists.
- command: [uv]
args: [sync]
cwd: backend-dir # Use "." to run from the worktree root.
skip-if: .venv # Skip this command if the file or directory exists.
A typical timeline looks like this:
Create a worktree
→ wte projects the environment and starts npm install in the background
→ The user describes the task; the AI reads, analyzes, and modifies the code
→ By the time the user or AI starts the project, dependencies are usually ready
Asynchronous initialization is started by the normal post-checkout hook, by the
Monitor Reconciler when it discovers an unregistered worktree, or by wte sync.
Configure an appropriate skip-if for each command in setup-commands to avoid
rerunning completed initialization during a later sync.
Commands supported by wte
wte init Create personal configuration and templates, and install core Git hooks
wte sync Synchronize the environment and start configured setup commands
wte list List port allocations for worktrees that still exist
wte doctor Diagnose configuration, profiles, registry, secrets, hooks, and Monitor
wte monitor enable Install or refresh optional host monitoring
wte monitor disable Remove host monitoring only, preserving Git hooks
wte uninstall Remove hooks and the Monitor, preserving configuration and runtime state
wte normally keeps worktrees synchronized automatically, so manual syncs are not
necessary. Only after updating a profile, if the latest configuration needs to be
projected again, run wte sync from the root of the target worktree.
License
Release files for worktree-env 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| worktree_env-0.7.0.tar.gz | 72.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| worktree_env-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 103.9 kB
Release files / worktree_env-0.7.0.tar.gz
| Download URL | worktree_env-0.7.0.tar.gz |
|---|---|
| Size | 72.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
de06c8e22e3474202a1723f9dd448be694b74ae23094a98a5b558e333e10a277
|
|
BLAKE2b-256 checksum How to use checksums |
a97864dd1e436b226ca820b465f1b2e76c32cfaf23ad60c9732a54a6bd8381a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / worktree_env-0.7.0-py3-none-any.whl
| Download URL | worktree_env-0.7.0-py3-none-any.whl |
|---|---|
| Size | 31.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3e224c575b65948d1f1cdaedd70a49f2776f816cc715d47906d9c8738271dbe1
|
|
BLAKE2b-256 checksum How to use checksums |
83697cec145d51339cce7dfc1f2d7a8679c56e5c8503c4ea4e0f2df87b8ea056
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|