Introduction
gwt (GitWorkTower) is a CLI tool that turns git worktrees into a complete development environment. It manages terminal sessions, a development reverse proxy, AI agent integrations, and a full merge pipeline — all from one command.
Why gwt?
Git worktrees let you work on multiple branches simultaneously without stashing or switching. But managing worktrees by hand gets tedious: creating them, setting up terminal layouts, remembering ports, cleaning up after merge.
gwt automates all of that. When you attach a repository, it creates a Zellij session with tabs for each worktree — each tab has a chat agent, a gateway dashboard, and a shell. When you’re done with a branch, one command verifies, rebases, squashes, merges, and cleans up.
How it works
gwt sits on top of three systems:
- Git worktrees — isolated working directories for each branch, stored in
.worktrees/ - Zellij — a terminal multiplexer that provides the tabbed session layout
- A reverse proxy (gateway) — routes dev apps via subdomains (
app.localhost:54846)
You configure your project once with gwt init, then gwt attach sets everything up. From there, gwt switch, gwt create, and gwt merge handle the branch lifecycle.
Optional: Diffity Code Review
If Node.js is available, gwt integrates with Diffity for browser-based code review with AI-powered comment handling. This is fully optional — gwt works without it.
Installation
From source
cargo install --path crates/gwt
Prerequisites
- Git — gwt manages git worktrees
- Zellij — terminal multiplexer for session management (install)
- Node.js (optional) — required for Diffity code review integration
Shell alias conflict
If you use oh-my-zsh, its git plugin defines gwt as an alias for git worktree. Add this to your .zshrc after the source $ZSH/oh-my-zsh.sh line:
unalias gwt 2>/dev/null
Shell completions
Install tab completions for your shell:
gwt completions install
Verify installation
Run gwt doctor to check that everything is set up correctly:
gwt doctor
✓ git repo (/path/to/project)
✓ branch (main)
✓ zellij (0.42.1)
✓ gwt.toml (found)
✓ worktrees (3 found in .worktrees)
✓ gateway apps (2 configured)
✓ diffity (ready)
Project Setup
Run gwt init in your repository root to configure gwt interactively.
The init wizard
gwt init
Setting up gwt for /path/to/project...
Project name: my-app
Gateway port: 54846
Add a gateway app? yes
App name: frontend
Command: npm run dev
Ready pattern: localhost
Add a gateway app? no
Install Claude Code hooks? yes
Writing .config/gwt.toml
Installed plugin to ~/.config/gwt/gwt.wasm
Done! Run `gwt attach` to start your session.
The wizard creates .config/gwt.toml in your project root. See the configuration reference for all available options.
What gets created
| File | Purpose |
|---|---|
.config/gwt.toml | Project configuration |
.gwt/ | Runtime state (PID files, logs) — gitignored |
.worktrees/ | Worktree directories — gitignored |
Diffity setup
If Node.js is available on your system, the gateway will automatically include the Diffity code review app. No extra configuration needed — gwt detects npx at runtime.
If you install Node.js later, Diffity will be available the next time you start the gateway. Run gwt doctor to check the current status.
Claude Code hooks
When you choose to install Claude Code hooks, gwt registers lifecycle hooks so that the AI agent is aware of worktree operations (branch switches, creates, removes). This enables the agent to react to your workflow automatically.
Your First Session
This walkthrough takes you from zero to a working gwt session.
1. Attach to a session
gwt attach --all
This starts a Zellij session named gwt (configurable via session.name) and creates tabs for all worktrees. You’ll see:
- Tabs at the top — one per worktree attached to this session
- Left pane — AI chat agent
- Top-right pane — gateway dashboard
- Bottom-right pane — shell
2. Create a feature branch
From within the session, create a new worktree:
gwt create feat/my-feature
This creates a worktree at .worktrees/feat-my-feature/ and adds a new tab to your session.
3. Switch to it
gwt switch feat/my-feature
The Zellij tab switches automatically. You’re now working in the new worktree.
4. Start the gateway
If you have gateway apps configured:
gwt gateway start
Your apps are now available at subdomain URLs:
gwt gateway running on http://localhost:54846
http://frontend.localhost:54846 -> :3001
http://diff.localhost:54846 -> :3002
5. Work on your feature
Write code, commit as usual. The worktree is a normal git checkout — all git commands work as expected.
6. Review your changes
If Diffity is available:
gwt diff
This opens a browser-based diff view where you can leave comments and have AI process them.
7. Merge when ready
gwt merge
This runs the full pipeline:
- Verify — runs pre-merge hooks
- Rebase — rebases onto the target branch
- Squash — combines commits into one with an AI-generated message
- Merge — fast-forward merges into the target branch
- Cleanup — removes the worktree and branch
8. Detach when done
To close the current workspace tab:
gwt detach
To close all tabs for this repo:
gwt detach --all
To leave the session running in the background (processes keep running), press Ctrl+Q inside Zellij.
Worktrees
What are git worktrees?
A git worktree is an additional working directory linked to the same repository. Each worktree has its own checked-out branch, but they all share the same .git directory. This means you can work on multiple branches simultaneously without stashing or cloning.
How gwt manages worktrees
gwt stores worktrees in .worktrees/ (configurable via worktree.path). Branch names are sanitized for filesystem use — feat/my-feature becomes .worktrees/feat-my-feature/.
Creating
gwt create feat/my-feature
Creates the worktree and runs post-create hooks. If you have hooks configured (like npm ci), they run automatically in the new worktree.
Switching
gwt switch feat/my-feature
If the worktree doesn’t exist yet, gwt auto-creates it (when worktree.auto-create is enabled, which is the default). Inside a Zellij session, the tab switches automatically. Outside Zellij, it prints the worktree path.
Removing
gwt remove feat/my-feature
Runs pre-remove hooks (Zellij closes the tab, gateway stops apps), removes the worktree directory, and deletes the branch (unless --keep-branch is passed).
You cannot remove the worktree you’re currently in — switch to a different one first.
Listing
gwt list
Shows all worktrees with the current one marked:
* main /path/to/project
feat/my-feature /path/to/project/.worktrees/feat-my-feature
fix/bug-123 /path/to/project/.worktrees/fix-bug-123
Configuration
See worktree configuration for available options.
Sessions
What is a session?
A gwt session is a Zellij terminal session that groups multiple repositories and worktrees into a tabbed interface. Each tab represents an attached workspace with a consistent layout:
- Left pane — AI chat agent
- Top-right pane — gateway dashboard
- Bottom-right pane — shell
Attaching a repository
gwt attach
This connects to the session (creating it if it doesn’t exist) without changing any tabs. To also create tabs for worktrees, use --all or specify a branch:
gwt attach --all # reconnect + create tabs for all worktrees
gwt attach feat/my-feat # reconnect + create tab for one branch
By default all repos are added to a session named gwt. You can override this per-project:
# .config/gwt.toml
[session]
name = "my-project"
Or per-invocation:
gwt attach --session my-project
Grouping workspaces
Use named sessions to create logical groups. For example, you might have:
- A
backendsession with your API and database repos - A
frontendsession with your web app and design system - The default
gwtsession for everything else
Detaching
gwt detach # close current workspace tab (CWD-detected)
gwt detach --all # close all workspace tabs for this repo
The session is auto-killed when no tabs remain.
Leaving a session without stopping it
Press Ctrl+Q inside Zellij to detach from the session. All processes keep running in the background — you can reconnect later with gwt attach.
To hard-kill a session, use:
gwt session stop <name>
Managing sessions
gwt session list # Show all active sessions
gwt session stop <name> # Stop a session
Configuration
See session configuration for available options.
Gateway
What is the gateway?
The gateway is a local reverse proxy that bundles your development apps under a single port with readable subdomain URLs. Instead of remembering localhost:3000, localhost:3001, localhost:8080, you access everything through subdomains:
http://frontend.localhost:54846
http://api.localhost:54846
http://diff.localhost:54846
How it works
When you run gwt gateway start:
- gwt reads the
[[gateway.apps]]from your config - Each app is started as a child process with a dynamically assigned port
- The gateway listens on a single port (default:
54846) and routes requests by subdomain - A dashboard at
http://localhost:54846shows all running apps and their status
The gateway monitors each app for readiness using the configured ready-pattern — a string to look for in the app’s stdout (e.g., "localhost" or "ready").
Built-in apps
The gateway automatically registers a diff app accessible at http://diff.localhost:<port> whenever the managed diffity runtime is healthy. No configuration needed. If the runtime is unhealthy or uninstalled, the dashboard shows the diff app as Unavailable — run gwt doctor --fix to repair it.
Managed Diffity Runtime
The built-in diff app is powered by diffity, which is a Node.js CLI with a native dependency (better-sqlite3). To avoid Node-version-mismatch crashes (common on machines with multiple Node versions via nvm/asdf/homebrew), gwt ships its own pinned Node and installs diffity into an isolated tree at ~/.gwt/node-runtime/.
This happens automatically during gwt init. The gateway always invokes that exact Node, never your system node or npx. If the install fails (offline, disk full, unsupported platform), gwt init continues but the diff app shows Unavailable in the dashboard — run gwt doctor --fix to retry.
After a gwt upgrade with new pinned versions, gwt gateway start will warn and disable the diff app until you run gwt doctor --fix.
Dashboard
Accessing http://localhost:<port> (without a subdomain) shows the gateway dashboard. It provides:
- A list of all running apps with their status (starting, ready, crashed, stopped)
- Direct links to each app’s subdomain URL
- Real-time status updates via WebSocket
URL Pane
Every worktree tab opened by gwt attach includes a dedicated urls pane that automatically runs gwt dashboard-url. This pane stays alive for the lifetime of the tab and displays the dashboard URL plus one line per configured app. It refreshes automatically whenever the gateway starts, stops, or its port changes — no manual action required.
Port Detection
By default the gateway uses ready-pattern (a plain string) to detect when an app is ready. For apps that print their bound port in startup output you can use ready-regex instead: a regular expression with one capture group whose match is parsed as the port number. This lets the gateway record the exact port the app chose rather than the one it was assigned.
Example: ready-regex = "listening on localhost:(\\d+)" captures the port from output like listening on localhost:3742.
Commands
gwt gateway start [--port <port>] # Start all apps and the proxy
gwt gateway stop # Graceful shutdown (SIGTERM)
gwt gateway kill # Force kill (SIGKILL)
gwt gateway status # Show running apps and ports
gwt gateway logs [app] [-n <lines>] # View app logs
Configuration
See gateway configuration for all options including app definitions.
Diffity (Code Review)
What is Diffity?
Diffity is a browser-based code review tool that integrates with gwt. It provides:
- Diff viewing — see your changes in a rich browser UI
- Comments — leave review comments on specific lines
- AI integration — have AI read, process, and resolve comments automatically
Prerequisites
Diffity requires Node.js. gwt detects whether npx is available at runtime — no configuration flag needed.
Check availability with gwt doctor:
✓ diffity (ready)
or:
✗ diffity (npx not found — install Node.js for code review features)
Usage
Opening a diff
gwt diff # Diff against HEAD
gwt diff main # Diff against a specific reference
gwt diff --uncommitted # Show only uncommitted changes
This opens the diff viewer in your browser.
Via the gateway
When the gateway is running, Diffity is accessible at http://diff.localhost:<port> — no need to run gwt diff separately.
How it fits together
gwt gateway start— if npx is available, registers Diffity as a gateway app automaticallygwt diff— opens the diff viewer directly, using the gateway port if the gateway is running- Code review workflow — leave comments in the browser, then use the AI integration to process them
Without Node.js
If Node.js is not installed, gwt works perfectly fine — you just don’t get the browser-based diff viewer. The gwt diff command will show a helpful message pointing you to the installation steps.
attach / detach
gwt attach
Attach to a Zellij session. Creates the session if it doesn’t exist, then reconnects.
gwt attach [<branch>] [--all] [--session <name>]
Options
| Option | Description | Default |
|---|---|---|
<branch> | Create tab for this branch (error if branch/worktree doesn’t exist) | — |
--all | Create missing tabs for all worktrees | false |
--session, -s | Session name | gwt (or session.name from config) |
Behavior
gwt attach— reconnects to the session; no tab changesgwt attach <branch>— reconnects and creates a tab for the given branch if missing; errors if the branch or worktree doesn’t existgwt attach --all— reconnects and creates missing tabs for all worktrees
Also available as
gwt session attach [<branch>] [--all] [--session <name>]
gwt detach
Remove workspace tabs from a session.
gwt detach [<branch>] [--all] [--session <name>]
Options
| Option | Description | Default |
|---|---|---|
<branch> | Close the tab for this branch | — |
--all | Close all workspace tabs for this repo | false |
--session, -s | Session name | gwt (or session.name from config) |
Behavior
gwt detach— closes the current workspace tab (detected via CWD)gwt detach <branch>— closes the tab for the specified branchgwt detach --all— closes all workspace tabs for this repo- The session is automatically killed when no tabs remain
Also available as
gwt session detach [<branch>] [--all] [--session <name>]
create / switch / remove / list
gwt create
Create a new worktree without switching to it.
gwt create <branch>
Behavior
- Creates a worktree at
<worktree.path>/<sanitized-branch>/ - Runs
post-createhooks (e.g., installs dependencies, adds a Zellij tab) - If a hook fails, the worktree creation is rolled back
gwt switch
Switch to a worktree. If it doesn’t exist and worktree.auto-create is enabled (default), creates it first.
gwt switch <branch>
Behavior
- Inside Zellij: switches to the worktree’s tab
- Outside Zellij: prints the worktree path (useful for
cd $(gwt switch feat/x)) - Runs
post-createhooks if the worktree was auto-created - Runs
post-switchhooks after switching
Configuration
Disable auto-creation:
[worktree]
auto-create = false
gwt remove
Remove a worktree.
gwt remove <branch> [--keep-branch] [--force]
Options
| Option | Description |
|---|---|
--keep-branch | Don’t delete the git branch |
--force, -f | Force removal even if the worktree has uncommitted changes |
Behavior
- You cannot remove the worktree you’re currently in
- Runs
pre-removehooks (closes Zellij tab, stops gateway apps) - Removes the worktree directory
- Deletes the branch unless
--keep-branchis passed - If the branch has unmerged commits, gwt prompts for confirmation before force-deleting
- In non-interactive mode (
GWT_AUTO_APPROVE=1), unmerged branches are kept with a warning
gwt list
List all worktrees.
gwt list
Output
* main /path/to/project
feat/my-feature /path/to/project/.worktrees/feat-my-feature
fix/bug-123 /path/to/project/.worktrees/fix-bug-123
The current worktree is marked with *.
merge / rebase / squash / verify
gwt merge
Full merge pipeline: verify, rebase, squash, merge, and cleanup.
gwt merge [target] [--keep]
Options
| Option | Description |
|---|---|
target | Branch to merge into (default: auto-detect upstream) |
--keep | Keep worktree and branch after merge |
Pipeline steps
- Verify — runs
pre-mergehooks - Rebase — rebases current branch onto target
- Squash — combines all commits into one (see below)
- Merge — fast-forward merges into target
- Post-merge hooks — runs
post-mergehooks (non-blocking) - Cleanup — removes worktree and branch (unless
--keep)
If you’re already on the target branch, the command errors out — switch to a feature branch first.
gwt verify
Run pre-merge hooks to check if the branch is ready to merge.
gwt verify
This is a shortcut for dispatching the pre-merge hook event. Configure what runs in [hooks.pre-merge].
gwt rebase
Rebase the current branch onto a target.
gwt rebase [target]
Options
| Option | Description |
|---|---|
target | Branch to rebase onto (default: auto-detect upstream) |
gwt squash
Squash all commits on the current branch into one.
gwt squash [target]
Options
| Option | Description |
|---|---|
target | Branch for merge-base calculation (default: auto-detect) |
AI commit messages
If merge.commit-message-command is configured, gwt passes the squash diff to the configured command and presents the generated message:
Generated commit message:
feat: add user authentication with OAuth2 support
Use this message? [Y/n/edit]
- Y (default) — use the generated message
- n — enter a message manually
- edit — open the message in
$EDITOR
If the command fails, falls back to manual input.
Configuration
[merge]
commit-message-command = "your-ai-tool summarize-commits"
gateway
Manage the development reverse proxy.
gwt gateway start
Start all configured apps and the proxy server.
gwt gateway start [--port <port>]
Options
| Option | Description | Default |
|---|---|---|
--port | Gateway listen port | 54846 (or gateway.port from config) |
Output
gwt gateway running on http://localhost:54846
http://frontend.localhost:54846 -> :3001
http://diff.localhost:54846 -> :3002
PID: 12345
Press Ctrl+C to stop.
If Diffity is available (npx + diffity resolvable), it’s automatically registered as the diff app.
gwt gateway stop
Graceful shutdown — sends SIGTERM to the gateway process.
gwt gateway stop
gwt gateway kill
Force kill — sends SIGKILL to the gateway process.
gwt gateway kill
gwt gateway status
Show whether the gateway is running and which apps are registered.
gwt gateway status
gwt gateway logs
View application logs.
gwt gateway logs [app] [-n <lines>]
Options
| Option | Description |
|---|---|
app | App name (omit to list available log files) |
-n, --lines | Show last N lines |
Examples
gwt gateway logs # List available log files
gwt gateway logs frontend # Show frontend stdout/stderr
gwt gateway logs frontend -n 50 # Last 50 lines
dashboard-url
Display the gateway dashboard URL and per-app URLs in the terminal.
Usage
gwt dashboard-url
Description
gwt dashboard-url is a long-lived command designed to run as a dedicated pane in each worktree tab. It reads .gwt/gateway.port and .gwt/config.toml to build and display the full list of URLs for the running gateway:
Dashboard
─────────
http://localhost:54812/
Apps
────
web: http://web.localhost:54812/
api: http://api.localhost:54812/
diff: http://diff.localhost:54812/
If the gateway has not started yet (.gwt/gateway.port does not exist), it displays:
Waiting for gateway to start...
The command watches .gwt/ for file changes and refreshes the display automatically, with a 200ms debounce.
Lifecycle
gwt dashboard-url stays alive until it receives SIGTERM (or the Zellij pane closes). The command exits cleanly on SIGINT. It is automatically placed in the urls pane of every worktree tab by gwt attach.
Environment
No flags or arguments. Reads from the current worktree’s .gwt/ directory.
session
Manage development sessions.
gwt session list
List all live Zellij sessions.
gwt session list
Only sessions that are currently running are shown. Sessions in an EXITED state are automatically pruned on every invocation. If no sessions are live, the command prints:
No active sessions.
gwt session stop
Stop a named session and clean up associated gateway processes.
gwt session stop <name>
On success the command prints:
Session '<name>' stopped. N gateways also stopped.
All gateway PID files belonging to worktrees managed by the session are removed. If the session is not currently active, the command exits with an actionable error message.
gwt session attach / detach
These are identical to gwt attach and gwt detach. See attach / detach.
diff
Open the Diffity diff viewer in the browser.
gwt diff [reference] [--uncommitted]
Options
| Option | Description | Default |
|---|---|---|
reference | Git reference to diff against | HEAD |
--uncommitted | Show only uncommitted changes | false |
Examples
gwt diff # Diff against HEAD
gwt diff main # Diff against main branch
gwt diff abc1234 # Diff against a specific commit
gwt diff --uncommitted # Only uncommitted changes
Prerequisites
Requires Node.js (npx). If not available, shows:
Diffity not available. Install Node.js and run: npx diffity
See also
- Diffity concept for the full code review workflow
- Gateway for accessing Diffity via subdomain
doctor
gwt doctor diagnoses the gwt installation and the managed diffity runtime.
Usage
gwt doctor # print a health report
gwt doctor --fix # repair any failing checks (reinstall runtime, etc.)
Checks
- git repo — the current directory is a git repository.
- zellij — the zellij binary is installed and runnable.
- gwt.toml — the project has been initialized (
.gwt/config.tomlexists). - worktrees — the worktree directory configured in
gwt.tomlexists. - Diffity runtime — the managed Node + diffity install under
~/.gwt/node-runtime/is healthy and matches the versions pinned in thisgwtrelease.
--fix
When --fix is passed, any failing check is repaired if a repair path exists. Currently this reinstalls the diffity runtime (wipes ~/.gwt/node-runtime/ and re-runs the same install that gwt init does).
Re-run after a gwt upgrade to pick up new pinned versions of Node or diffity.
hook
Dispatch a lifecycle hook event manually.
gwt hook <event>
Arguments
| Argument | Description |
|---|---|
event | Hook event name (e.g., post-create, pre-merge) |
Available events
| Event | Triggered by | Blocking |
|---|---|---|
post-create | gwt create, gwt switch (auto-create) | Yes |
post-switch | gwt switch | Yes |
pre-remove | gwt remove | Yes |
pre-merge | gwt verify, gwt merge | Yes |
post-merge | gwt merge | No |
Blocking hooks abort the operation on failure. Non-blocking hooks log warnings but continue.
Usage
This command is primarily used by external tools (like Claude Code) that need to trigger gwt hooks programmatically. For example, Claude Code calls gwt hook claude-stop when the AI agent stops.
See also
- Hooks configuration for configuring custom hook commands
Utilities
gwt init
Interactive project setup wizard. See Project Setup for a detailed walkthrough.
gwt init
Configures: project name, gateway port, gateway apps, Claude Code hooks. Creates .config/gwt.toml and installs the Zellij WASM plugin.
gwt doctor
Check environment health.
gwt doctor
Output
gwt doctor
✓ git repo (/path/to/project)
✓ branch (main)
✓ zellij (0.42.1)
✓ gwt.toml (found)
✓ worktrees (3 found in .worktrees)
✓ gateway apps (2 configured)
✓ diffity (ready)
Checks: git repository, current branch, Zellij availability, config file, worktree directory, gateway apps, and Diffity availability.
gwt skills install
Install recommended AI skills from the project config.
gwt skills install
Installs skills listed in skills.recommend:
[skills]
recommend = ["skill-one", "skill-two"]
gwt completions
Manage shell tab completions.
gwt completions install # Install for your current shell
gwt completions print <shell> # Print completion script (bash, zsh, fish, elvish, powershell)
gwt.toml Reference
gwt reads its configuration from .config/gwt.toml in your project root. All sections are optional — sensible defaults are used for everything.
[project]
[project]
name = "my-app" # Project display name (default: directory name)
[session]
[session]
name = "my-session" # Zellij session name (default: "gwt")
All repos with the same session name share a single Zellij session. Use different names to create logical groups.
[gateway]
[gateway]
port = 54846 # Optional: pin the gateway port. Omit to auto-allocate on first attach.
watch = false # Watch mode (default: false)
Port behaviour: When port is omitted (the default after gwt init), the gateway allocates a free port on first start and persists it to .gwt/gateway.port. Subsequent starts reuse that port while it remains available. Set port explicitly only if you need a stable, pinned port (e.g. for shared team configuration).
Gateway apps
gwt init auto-detects dev apps from package.json scripts, npm/pnpm workspaces, convention directories (apps/*, packages/*, services/*), Cargo workspace members with web-server dependencies, and Python project files. Detected apps are offered as a pre-selected multi-select during init.
You can add or edit apps manually at any time:
[[gateway.apps]]
name = "frontend"
command = "pnpm"
args = ["run", "dev"]
cwd = "apps/web" # Working directory relative to project root (default: ".")
port-env = "PORT" # Environment variable for port assignment
port-arg = "--port" # CLI argument for port (alternative to port-env)
ready-pattern = "localhost" # String to detect in stdout when app is ready
Each app gets a dynamically assigned port and is accessible via http://<name>.localhost:<gateway-port>.
Port assignment: Use either port-env (sets an environment variable) or port-arg (appends a CLI argument). The gateway assigns a random available port.
Ready detection: The gateway watches stdout for the ready-pattern string to determine when the app is serving. For apps that print their bound port in startup output, use ready-regex instead:
| Field | Type | Default | Description |
|---|---|---|---|
ready-regex | string | — | Regex with one capture group to extract the port from stdout/stderr. Overrides ready-pattern for port detection. Example: "localhost:(\\d+)" |
[worktree]
[worktree]
path = ".worktrees" # Directory for worktrees (default: ".worktrees")
auto-create = true # Auto-create on switch (default: true)
[merge]
[merge]
commit-message-command = "your-tool summarize" # Command for AI commit messages (default: none)
When set, gwt squash pipes the diff to this command and uses its stdout as the commit message. The user can accept, reject, or edit the result.
[skills]
[skills]
recommend = ["skill-one", "skill-two"] # Skills to install via `gwt skills install`
[hooks.*]
See Hooks for hook configuration.
Extra sections
Any unrecognized top-level sections are passed through as plugin configuration. Plugins deserialize their own config from these extra sections.
Hooks
Hooks let you run custom commands in response to worktree lifecycle events.
Configuration
Define hooks in .config/gwt.toml under [hooks.<event>]:
[hooks.post-create]
install = "npm ci"
setup = "cp .env.example .env"
[hooks.pre-merge]
lint = "npm run lint"
test = "cargo test"
[hooks.pre-remove]
cleanup = "rm -rf node_modules"
Each key under the event is a named command. Names are for identification only — the values are the shell commands that run.
Available events
| Event | When it fires | Blocking |
|---|---|---|
post-create | After a worktree is created | Yes |
post-switch | After switching to a worktree | Yes |
pre-remove | Before a worktree is removed | Yes |
pre-merge | Before merge verification starts | Yes |
post-merge | After a successful merge | No |
Blocking events abort the operation if any hook command fails. For example, if a pre-merge hook fails, gwt merge stops before rebasing.
Non-blocking events log a warning on failure but continue the operation.
Built-in hook handlers
In addition to your custom commands, gwt’s internal plugins also respond to hook events:
| Plugin | Event | Action |
|---|---|---|
| Zellij | post-create | Creates a tab for the new worktree |
| Zellij | pre-remove | Closes the worktree’s tab |
| Zellij | post-switch | Switches to the worktree’s tab |
| Gateway | post-switch | Cleans up stale PID files |
| Claude | Various | Keeps the AI agent in sync with worktree state |
Custom hooks run alongside these built-in handlers.
Manually dispatching hooks
gwt hook post-create
gwt hook pre-merge
See hook command for details.
Troubleshooting
gwt doctor
The quickest way to diagnose problems:
gwt doctor
This checks all prerequisites and shows what’s working and what’s not.
Common issues
gwt command not found
If you use oh-my-zsh, its git plugin aliases gwt to git worktree. Add to your .zshrc:
unalias gwt 2>/dev/null
Zellij not found
Install Zellij: zellij.dev/documentation/installation
gwt requires Zellij for session management. Without it, gwt attach will fail.
Gateway port already in use
If gwt gateway start fails because the port is taken:
gwt gateway status # Check if a gateway is already running
gwt gateway stop # Stop the existing one
Or use a different port:
gwt gateway start --port 9000
Stale PID file
If gwt gateway status shows “running” but the process is dead:
gwt gateway stop # Cleans up the stale PID file
The gateway also cleans up stale PID files automatically on post-switch hooks.
Diffity not available
✗ diffity (npx not found — install Node.js for code review features)
Install Node.js to enable Diffity. gwt detects it at runtime — no reconfiguration needed. After installing, verify with gwt doctor.
Cannot remove current worktree
Cannot remove the current worktree. Switch to a different worktree first.
Switch to another branch before removing:
gwt switch main
gwt remove feat/old-branch
Worktree does not exist (auto-create disabled)
Worktree for 'feat/x' does not exist. Use 'gwt create feat/x' first.
Either create the worktree explicitly or enable auto-creation:
[worktree]
auto-create = true
Branch not fully merged
When gwt remove or gwt merge warns about an unmerged branch, it keeps the branch to prevent data loss. If you’re sure the branch is safe to delete:
git branch -D feat/old-branch