Release Notes
Improved
-
Hook scripts read renamed context keys: the JSON piped to every hook drops
worktree,repo_root,main_worktree, andmain_worktree_pathin favour ofworktree_path,repo_path,repo, andprimary_worktree_path. Config templates migrate on load, so hooks written with the old names keep working; scripts reading the JSON,--executetemplates, and--varoverrides need updating. (Breaking.) (#4080) -
wt config plugins piinstalls for Pi; oh-my-pi moves towt config plugins omp:pi installwrote an oh-my-pi hook, which Pi never loads; Pi users re-run it to get a Pi extension. Existing oh-my-pi hooks keep working, listed as outdated untilwt config plugins omp install. (Breaking: scripts that runwt config plugins pifor oh-my-pi needomp.) Docs (#4135, thanks @ashebanow) -
Interactive prompts no longer open with a blank line:
wt config shell install,wt config plugins claude install, and the commit-message setup offer inwt mergebegan with one. (#4059) -
wt config showgives a singlewt config shell installhint: an outdated wrapper, a fish wrapper at the deprecatedconf.dpath, and missing fish completions each printed their own hint. The zshcompinitsnippet is now syntax-highlighted. (#4059) -
wt config update --output <path>confirms the write: it now prints✓ Wrote user config migration @ ~/migrated.tomlwhere it printed nothing.--output=-stays silent. (#4053)
Fixed
-
Removal no longer discards untracked files hidden by
status.showUntrackedFiles = no: under that setting, a worktree holding only untracked files looked clean, sowt remove,wt merge, andwt step pruneremoved it without--force. Safety checks now always count untracked files, and auto-staging warnings list every file they stage. (#4111, thanks @Duang777) -
wt removeandwt mergecheck the right worktree under an inheritedGIT_DIR: run from a!wtgit alias or a git hook, which passGIT_DIRdown,wt removecould delete a dirty worktree without--force, andwt mergecould refuse a clean merge. Both now check the worktree they act on. (#4082, thanks @Duang777) -
wt mergeno longer deletes a worktree locked withgit worktree lock: the merge completes and printsWorktree preserved (locked). Every Worktrunk removal refuses a locked worktree, even with--force; rungit worktree unlock <path>first. (#4073, thanks @Duang777) -
wt config updateno longer overwrites a config edited while its prompt is open: it wrote the pre-prompt snapshot over the newer file and reported success. It now fails with the path and leaves the newer file; re-running previews the current contents. (#4127, thanks @zach-hammad-vs for reporting) -
wt step pruneno longer deletes a branch created minutes ago from an older commit:--min-ageaged a branch without a worktree by its commit's date, so a new branch off a day-old commit was pruned on the next run. Age now comes from the branch's oldest reflog entry. (#4077) -
A comment or blank line above an inline config section no longer breaks the config: a save or migration that rewrote
commit = { … }as[commit]put that line inside the brackets.wt switch,wt merge,wt remove, and severalwt stepcommands then failed withFailed to load config;wt listwarned and ran without the config. Saves also dropped unrecognized keys from inline sections. (#4120, thanks @zach-hammad-vs for reporting) -
Config saves keep the trailing comment on a value they rewrite: a save such as answering the commit-generation offer dropped the comment after each value it rewrote, including one after an inline section's closing brace. (#4138)
-
Ignored project-config keys warn from every worktree: an unknown key in
.config/wt.toml, or one that belongs in user config such as[merge], warned only in the main worktree, sowt mergefrom a linked worktree ignored it silently. (#4145, thanks @ee-prog for reporting) -
wt mergeskipspost-commitwhen it removes that hook's worktree: before, the hook ran in the primary worktree when the removed one sat inside the repository, and otherwise didn't run. The merge now prints▲ Skipped post-commit; move work that must finish there topre-remove. (#4049) -
wt switchrecovers from a removed worktree in a bare repository:wt switchand the picker surfaced git's rawfatal: Unable to read current working directoryinstead, and the removed-directory message lackedwt switch ^. (#4067) -
wt config shell install fishputs the wrapper where fish reads it under a custom$XDG_CONFIG_HOME: the wrapper always went to~/.config/fish/functions/, so install reported success whilewt switchchanged no directory. On Windows, the fish completion moves from%APPDATA%\fish\completionsto the wrapper's directory. (#4107) -
wt config shell install zshhonours$ZDOTDIRonly when it is absolute: an empty or relative value resolved against the current directory, so install appended to a.zshrcunder the current directory and reported success while the file zsh reads went untouched. A non-absolute value now falls back to$HOME. (#4085) -
wt config shell installfor Nushell no longer deletes awt.nuunder the current directory: an empty or relative$XDG_CONFIG_HOMEresolved against it, so install removed anushell/vendor/autoload/wt.nubeneath it unread, as a stale wrapper. It now falls back to~/.config, which also fixes unreadtealogins that letwt switch pr:<n>treat a Gitea host as GitHub. (#4104) -
wt switch --createnames the branch a failed worktree add leaves behind:git worktree add -bwrites the branch ref before populating the worktree, so a failure in between left the branch with no worktree, and the next attempt reported a name collision. The error now says how to delete or reuse it. (#4109, thanks @technicalpickles for reporting) -
wt config shell installno longer tells an already-wrapped shell to restart: reinstalling from a shell with the wrapper loaded, such as after a version bump, printed↳ Restart shell to activate shell integration. (#4059) -
wt config shell installmigrates a deprecatedconf.dfish wrapper when~/.config/fish/functionsis missing: before, onlywt config shell install fishmigrated it.wt config shownow reports that wrapper even when fish isn't onPATH. (#4059) -
Plugin status and uninstall checks ask the agent CLIs:
wt config showcould list an installed Claude Code plugin or Gemini CLI extension as not installed, andwt config plugins claude|codex uninstallcould report success over a marketplace still configured. A repeat Claude uninstall now runs both removals instead of stopping atPlugin not installed. (#4048, #4054) -
wt config plugins opencode installno longer writes the plugin into the current directory: withOPENCODE_CONFIG_DIRset but empty, it wroteplugins/worktrunk.tsthere, typically inside a repository, reporting success though OpenCode never saw it. An empty value now counts as unset. (#4084) -
The OpenCode plugin clears its activity marker when OpenCode 1 disposes it: only explicit session deletion cleared it, so a finished session kept its
💬andwt listshowed it as active untilwt config state marker clearwas run by hand. (#4129, thanks @drewdas for reporting) -
wt config updatemigrates[ci] platforminto a[forge]section that only setshostname: that config, common with GitHub Enterprise and self-hosted GitLab, kept the deprecated key with no deprecation warning. (#4061) -
Deprecated keys in an inline
[projects]entry now migrate:"example.com/org/repo" = { merge = { no-ff = true } }skipped the migration the section form got, so the per-project setting was ignored. (#4122, thanks @zach-hammad-vs for reporting) -
A deprecated config section the migration can't rewrite now warns:
select = "not a table", or a[select]whose[switch.picker]destination is already set, was ignored without a message. It now reportsunknown field select (will be ignored). (#4121, thanks @zach-hammad-vs for reporting) -
wt config updateno longer changes what a template renders: it could rename{{ repo_root }}inside a quoted string, and after{% set %}rebound it. A template binding a retired name or its replacement stays unmigrated and unwarned: a hook reading that name fails, and a squash template can silently drop it. (#4124, thanks @zach-hammad-vs for reporting) -
Config migration previews ignore
diff.externaland report a failed diff:wt config showand thewt config updateprompt showed an external diff program's output in place of the patch, and nothing when the diff failed. A failure now printsCould not render the proposed diffwith git's error. (#4126, thanks @zach-hammad-vs for reporting) -
An alias that binds
dry_runcan take--dry-run <value>: that spelling failed with the retired-flag error even when the template referenced{{ dry_run }}, while--dry-run=<value>worked. A bare--dry-runstill errors; pass--dry-run=1. (#4058) -
wt <alias>outside a repository names the alias: it fell through to clap'sunrecognized subcommand, which suggested the name just typed. It now reports that aliases only run inside a git repository and quotes git's own error. (#3982, thanks @yzx9)
Documentation
-
Shell integration has its own page: Shell integration was previously skill-only. The site gains a footer, the
wt configandwt steppages stop repeating the global options under every subcommand, the generated--helpblocks drop their copy button, and three recipes are corrected — including the OpenCode-xform, wrong since 0.76.0. (#4000) -
wt hook --helpdescribespost-mergecorrectly: it said the hook runs in the destination worktree "with removal", a condition that never existed — it always runs there. (#4071, thanks @kyle641320 for reporting) -
Docs heading anchors are scoped to their subcommand:
/step/#min-age-guardis now/step/#wt-step-prune--min-age-guard, so headings repeated across subcommands no longer collide. Links to the old anchors land at the top of the page. (#4079) -
Docs copy buttons sit with their code: the button sat 40px below a block's top — on line two, or hanging below a one-line block — and a file-name tab sat inset. A block of several commands now gives each its own button, not one copying all, and trailing shell comments stay out of what's copied. (#4090, #4091, #4078)
-
Demos re-recorded with the site's palette: dark-theme demos showed a light Zellij bar and a light Claude Code theme. (#4069)
Internal
- The Nix flake uses
stdenv.hostPlatform.isDarwin, which stops the deprecation warning nixpkgs prints on every eval. (#4132, thanks @ashebanow)
Install worktrunk 0.78.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/max-sixty/worktrunk/releases/download/v0.78.0/worktrunk-installer.sh | sh && wt config shell installInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/max-sixty/worktrunk/releases/download/v0.78.0/worktrunk-installer.ps1 | iex"; git-wt config shell installInstall prebuilt binaries via Homebrew
brew install worktrunk && wt config shell installDownload worktrunk 0.78.0
| File | Platform | Checksum |
|---|---|---|
| worktrunk-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| worktrunk-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| worktrunk-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| worktrunk-aarch64-unknown-linux-musl.tar.xz | ARM64 MUSL Linux | checksum |
| worktrunk-x86_64-unknown-linux-musl.tar.xz | x64 MUSL Linux | checksum |
Install via Cargo
cargo install worktrunk && wt config shell installInstall via Winget (Windows)
winget install max-sixty.worktrunk && git-wt config shell installInstall via AUR (Arch Linux)
paru worktrunk-bin && wt config shell install