Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

FilePurpose
.config/gwt.tomlProject 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:

  1. Verify — runs pre-merge hooks
  2. Rebase — rebases onto the target branch
  3. Squash — combines commits into one with an AI-generated message
  4. Merge — fast-forward merges into the target branch
  5. 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 backend session with your API and database repos
  • A frontend session with your web app and design system
  • The default gwt session 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:

  1. gwt reads the [[gateway.apps]] from your config
  2. Each app is started as a child process with a dynamically assigned port
  3. The gateway listens on a single port (default: 54846) and routes requests by subdomain
  4. A dashboard at http://localhost:54846 shows 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

  1. gwt gateway start — if npx is available, registers Diffity as a gateway app automatically
  2. gwt diff — opens the diff viewer directly, using the gateway port if the gateway is running
  3. 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

OptionDescriptionDefault
<branch>Create tab for this branch (error if branch/worktree doesn’t exist)
--allCreate missing tabs for all worktreesfalse
--session, -sSession namegwt (or session.name from config)

Behavior

  • gwt attach — reconnects to the session; no tab changes
  • gwt attach <branch> — reconnects and creates a tab for the given branch if missing; errors if the branch or worktree doesn’t exist
  • gwt 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

OptionDescriptionDefault
<branch>Close the tab for this branch
--allClose all workspace tabs for this repofalse
--session, -sSession namegwt (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 branch
  • gwt 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-create hooks (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-create hooks if the worktree was auto-created
  • Runs post-switch hooks after switching

Configuration

Disable auto-creation:

[worktree]
auto-create = false

gwt remove

Remove a worktree.

gwt remove <branch> [--keep-branch] [--force]

Options

OptionDescription
--keep-branchDon’t delete the git branch
--force, -fForce removal even if the worktree has uncommitted changes

Behavior

  • You cannot remove the worktree you’re currently in
  • Runs pre-remove hooks (closes Zellij tab, stops gateway apps)
  • Removes the worktree directory
  • Deletes the branch unless --keep-branch is 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

OptionDescription
targetBranch to merge into (default: auto-detect upstream)
--keepKeep worktree and branch after merge

Pipeline steps

  1. Verify — runs pre-merge hooks
  2. Rebase — rebases current branch onto target
  3. Squash — combines all commits into one (see below)
  4. Merge — fast-forward merges into target
  5. Post-merge hooks — runs post-merge hooks (non-blocking)
  6. 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

OptionDescription
targetBranch to rebase onto (default: auto-detect upstream)

gwt squash

Squash all commits on the current branch into one.

gwt squash [target]

Options

OptionDescription
targetBranch 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

OptionDescriptionDefault
--portGateway listen port54846 (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

OptionDescription
appApp name (omit to list available log files)
-n, --linesShow 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

OptionDescriptionDefault
referenceGit reference to diff againstHEAD
--uncommittedShow only uncommitted changesfalse

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

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.toml exists).
  • worktrees — the worktree directory configured in gwt.toml exists.
  • Diffity runtime — the managed Node + diffity install under ~/.gwt/node-runtime/ is healthy and matches the versions pinned in this gwt release.

--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

ArgumentDescription
eventHook event name (e.g., post-create, pre-merge)

Available events

EventTriggered byBlocking
post-creategwt create, gwt switch (auto-create)Yes
post-switchgwt switchYes
pre-removegwt removeYes
pre-mergegwt verify, gwt mergeYes
post-mergegwt mergeNo

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

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:

FieldTypeDefaultDescription
ready-regexstringRegex 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

EventWhen it firesBlocking
post-createAfter a worktree is createdYes
post-switchAfter switching to a worktreeYes
pre-removeBefore a worktree is removedYes
pre-mergeBefore merge verification startsYes
post-mergeAfter a successful mergeNo

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:

PluginEventAction
Zellijpost-createCreates a tab for the new worktree
Zellijpre-removeCloses the worktree’s tab
Zellijpost-switchSwitches to the worktree’s tab
Gatewaypost-switchCleans up stale PID files
ClaudeVariousKeeps 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