CLI Reference¶
Run kwt <command> --help for command-specific flags. This page summarizes the
stable command surface.
| Command | Purpose |
|---|---|
kwt, kwt tui |
Open the cross-project and multi-machine dashboard. |
kwt add |
Create a worktree and optionally launch its workspace. |
kwt branches |
List branches available for a new worktree. |
kwt open |
Open or establish a worktree workspace session. |
kwt list |
List worktrees. |
kwt status |
Show Git status, sync state, and activity. |
kwt projects |
List registered project repositories. |
kwt pr |
Discover and import pull requests through JSON. |
kwt get |
Print a matching worktree path. |
kwt cd |
Open a shell in a matching worktree. |
kwt exec |
Run a command in a matching worktree. |
kwt remove |
Delete a worktree, optionally its branch. |
kwt prune |
Clean up stale Git worktree metadata. |
kwt sync |
Publish and inspect multi-machine sync state. |
kwt tmux |
Manage standalone tmux sessions. |
kwt workspace |
Manage directory workspaces. |
kwt config |
Read and write config values. |
kwt completion |
Generate shell completion and integration. |
kwt version |
Show version and build information. |
Examples¶
kwt add -b fix/parser-race
kwt add --from origin/fix/review fix/review
kwt branches --json
kwt open parser
kwt open /path/to/worktree --start-session
kwt status
kwt pr list --project github.com/acme/widget --json
kwt pr import 17 --project github.com/acme/widget \
--start-session --json
kwt sync status
kwt exec fix/parser-race -- go test ./internal/parser
kwt workspace add ~/notes
kwt workspace list
kwt config get layouts.default
kwt config set --local layouts.default stack
When kwt add -b creates a branch, it fetches origin and starts from its
default branch. If that remote base is unavailable, it falls back to local
main, then master, then the branch checked out in the primary worktree.
kwt add <branch> checks out an existing local branch. Use
kwt add --from <remote-ref> <branch> when a remote candidate must become a
local tracking branch. Shorthand such as origin/topic is accepted, but kwt
verifies it against fetched refs and passes the full refs/remotes/... identity
to Git. Neither existing-branch path copies files, runs setup commands, or
launches a workspace; --layout and --select-layout are rejected.
Git branch mutation and checkout run with an empty hooks directory, configured
smudge and process filters disabled, and kwt credential variables removed.
Environment references in the source-derived branch name remain literal when
kwt builds the destination path. Submodules are not recursively updated during
creation; inspect the superproject first, then update submodules explicitly
after acknowledgement.
The created worktree participates in ordinary status and fleet observation.
Review the checkout and run kwt open <worktree> as the explicit
acknowledgement that opts in to its layout and pane commands.
kwt branches --json emits only candidates not already checked out, with
name, a source-qualified display label, the full source ref, and
is_remote fields for interactive clients.
kwt open¶
With no argument, kwt open fuzzy-picks a worktree. A pattern narrows the
cross-project list and opens the sole match directly. Kwt creates or repairs
the canonical tmux workspace with its resolved layout before attaching.
An exact worktree-root path is resolved directly from Git before pattern matching, including registered primary checkouts and linked worktrees outside the configured global worktree base.
On Unix-like systems, an external ordinary or protected attachment replaces
the kwt process with the tmux client. tmux therefore owns signal handling and
the final exit status, and no waiting kwt parent remains. Windows retains a
waiting parent because it has no Unix process-replacement primitive.
An ordinary open from inside tmux switches the current client instead.
Protected attachment always remains external because it targets a separate
workspace-specific socket.
kwt open <exact-worktree-path> --start-session performs the same layout and
session bootstrap without attaching a client. Use it before an external
ordinary tmux client attaches to a session that may not exist yet. The exact
path is resolved directly from Git rather than the global worktree base, which
keeps this automation mode noninteractive and supports linked worktrees stored
outside that base.
Protected pull-request imports remain restricted to kwt pr attach.
kwt list¶
--json emits an array of objects with path, branch, commit_hash, is_main,
created_at (worktree directory mtime), generation (the durable identity for
conditional removal), repository (the host/owner/name slug, or a
local/<path> fallback for a repository without a usable remote — see below),
and session_name (the tmux workspace session name kwt attaches to).
An imported pull-request worktree additionally includes tmux_socket_name for
its protected workspace-specific server. To converge on the same session, run
kwt open <path> --start-session before an ordinary attach-only client, or
kwt pr attach <path> when tmux_socket_name is present. This lets kwt create
the session when needed without a client creating it bare or bypassing its
protected attach policy. See Attaching from other
tools before using new-session.
kwt open and dashboard open actions refuse protected pull-request imports
and direct the user through kwt pr attach.
created_at and generation are populated in both local and -g mode.
kwt remove¶
--if-generation <id> makes a single-worktree removal conditional on the
generation value returned by kwt list --json. Kwt stores this random
identity in the worktree's Git administrative directory and compares it while
holding the repository's worktree-mutation lock, so automation cannot delete a
replacement checkout created at the same path. Ordinary directory changes do
not alter the generation.
kwt projects¶
--json emits an array of the registered project repositories ({repository,
name, path, last_touched}), so external automation can discover main-repo
paths that live outside the configured worktree base directory without
parsing the config file. repository uses the same host/owner/name slug as
kwt list --json's repository field, so the two surfaces can be joined.
kwt projects add <path> registers an existing Git checkout without opening
the dashboard. A linked-worktree path resolves to its main repository before
registration, and repeating the command updates the existing entry rather than
adding a duplicate.
With --json, success returns:
{
"status": "registered",
"project": {
"repository": "github.com/kenn-io/kwt",
"name": "kwt",
"path": "/code/kwt",
"last_touched": "2026-07-27T11:16:16Z"
}
}
Failures return a stable error envelope on stdout. invalid_repository exits
2; registration_failed exits 1. Both are non-retryable:
{
"error": {
"code": "invalid_repository",
"message": "/missing is not an accessible Git repository",
"retryable": false
}
}
kwt pr¶
kwt pr list and kwt pr import are the noninteractive, structured boundary
for pull-request clients. kwt owns provider calls, ref handling, branch and
workspace naming, normal worktree creation and setup, push configuration,
provenance, and tmux session naming. See Pull-request
automation for the JSON and exit-status contract.
Every imported workspace record includes tmux_socket_name.
pr import --start-session additionally establishes a blank shell-only
session without attaching, for clients that provide their own ordinary tmux
presentation. It does not execute configured layouts or agent commands.
Attach with kwt pr attach <workspace.path>, which verifies the persisted
identity, creates or repairs that protected blank session when needed, and
uses attach-session -E. This is an interactive exception to the PR JSON
contract: failures before attachment remain structured, but after a successful
Unix process replacement tmux owns terminal output and the final exit status.
Repository identity fallback¶
A repository's repository slug is derived from its origin remote
(host/owner/name). A repository with no usable remote instead gets a
deterministic path-based fallback of the form local/<absolute-path> (path
separators normalized to /). Every surface that reports repository identity —
kwt list --json, kwt list -g --json discovery, and kwt projects --json —
resolves it through the same code, so the fallback is identical across all
three and the surfaces remain joinable even for local-only repositories.
Workspace session bootstrap¶
Every workspace session kwt creates (add, open, and the TUI) applies the
same bootstrap so panes are indistinguishable regardless of which client
attaches. kwt sets default-command to the empty string, which tells tmux to
start its configured default-shell natively instead of passing a command
string through $SHELL -c. This supports valid non-POSIX shells such as fish
and tcsh and avoids running startup hooks in an extra non-login shell. For the
first pane, kwt queries the session's resolved default-shell and executes it
directly with -l after applying the session environment bootstrap. This
login-shell behavior is the parity mechanism across launchers; panes otherwise
see tmux's own TERM_PROGRAM/TERM_PROGRAM_VERSION, which tmux sets in every
pane regardless of what kwt does.
Session creation uses an inert first-pane placeholder to make that environment
ordering safe. new-session starts sleep 2147483647 as separate argv words,
so no user shell or profile runs before the session exists. kwt then installs
the session remove-markers, resolves default-shell, and replaces the
placeholder with <resolved-shell> -l. Only after that respawn does it create
the remaining panes. Thus every real shell starts after launcher state has been
masked, and the first pane's startup files run exactly once.
kwt treats a canonical set of variables as launcher state — scoped to the terminal, shell, or tool that launched kwt, not to the workspace session tmux hosts — and applies it in two places that share a single definition, with one explicit, documented exception, so they cannot silently drift apart:
- Exec-time sanitization. Every tmux invocation kwt makes execs tmux with
these variables removed from its own environment, EXCEPT
EDITORandVISUAL. If no tmux server is running yet, the invocation that starts one is what seeds that server's GLOBAL environment table; sanitizing at exec time keeps launcher state out of that table in the first place, rather than only masking it later per session. (PWD/OLDPWD/SHLVL/_are included even though they aren't terminal-integration variables: worktree directories are passed to tmux via-c, and shells re-derive these on their own.)EDITOR/VISUALare kept here because tmux itself reads them at server start to choose its default key mode (status-keys/mode-keys: vi vs. emacs), and a user'stmux.confmay consult them too; stripping them from the server's own exec environment would silently flip that behavior for every kwt-started server. - Session remove-markers. The same variables — including
EDITORandVISUAL, with no exception — are also removed from each session with a session-scoped remove-marker (set-environment -r), which masks the global/server value for that session only without touching other sessions or the server-wide environment. This covers sessions created against an already-running server whose global table predates kwt's exec-time sanitization (e.g. a server another tool started), and it is what keepsEDITOR/VISUALout of every pane's shell even though the server process itself now keeps them.
The full list: exact names __CFBundleIdentifier, EDITOR,
KWT_FLEET_TOKEN, KWT_GITHUB_TOKEN, OLDPWD, PROMPT, PROMPT_COMMAND,
PWD, RPROMPT, SHLVL, TERM_PROGRAM, TERM_PROGRAM_VERSION, VISUAL,
WINDOWID, _; and prefixes ALACRITTY_, CONDA_, FZF_, ITERM,
KITTY_, NVM_, PYENV_, STARSHIP_, VIRTUAL_ENV, WEZTERM_, WT_,
VSCODE_. Every kwt workspace also removes the exact variable named by
fleet.token_env, case-insensitively, both from tmux subprocesses and from
the session environment. Operational kwt variables such as KWT_HOME are
preserved. EDITOR and VISUAL are excluded from exec-time sanitization
only, per above; every other name in this list is treated identically by both
mechanisms.
TERMINFO is deliberately excluded from the whole list: it is functional
terminal configuration (a custom terminfo database path), not transient
launcher-integration state, and is needed for tmux attach rendering and for
pane applications resolving tmux's own TERM.
Attaching from other tools¶
kwt applies this bootstrap when it creates a session. A session that some
other tool created — for example with tmux new-session -A -s <session_name>,
which attaches if the session exists but otherwise creates it bare — starts
without the default-command and remove-markers, so its windows would not
match kwt's until repaired.
Two rules keep external tools consistent with kwt:
- When the session already exists, attach only: use
tmux attach-session -t <session_name>(orswitch-client -tfrom inside tmux). Attach-only commands never create a bare session, so there is nothing to repair. - If your tool creates the session itself, apply the equivalent bootstrap:
set
default-commandto""and add a session-scoped remove-marker (set-environment -r <name>) for each launcher variable listed above (includingEDITOR/VISUAL— the exec-time exception above applies only to how the tmux server process itself was started, not to the session remove-markers). To keep the first pane clean as well, create it with an inert direct-argv placeholder, install the markers, resolve the session'sdefault-shell, and respawn that pane with the resolved shell and-l.
kwt is also self-healing here: the next time it attaches to a session it finds
already running, it re-applies the safe bootstrap subset (default-command
plus the remove-markers — never construction or pane commands), so a session
another tool created bare converges on consistent behavior for windows opened
after that attach.
PR imports use a stricter reuse boundary. Every import reports a deterministic,
workspace-specific socket. pr import --start-session and pr attach create
one blank shell session, record the canonical workspace path, and reuse only a
same-named session with that exact marker. They never execute configured layout
or agent commands. The isolated server starts without the provider or
configured fleet credential. The protected session also masks those names and
filters them from update-environment. Because tmux options are mutable,
pr attach enforces attach-session -E rather than trusting the current
option value.
The repair path deliberately does not rewrite panes in an externally created session that is already running; it only makes future windows consistent. In a session kwt creates itself, the inert-placeholder/respawn sequence also covers the first pane, including when the tmux server was already running with launcher variables in its global environment table.
Exit behavior¶
Commands intended to launch the dashboard or attach to tmux require an
interactive terminal. In non-interactive contexts, use data-oriented commands
such as list, status, get, and exec.