v0.46.0

max-sixty/worktrunkv0.46.0Apr 29, 2026by github-actions[bot]

Upgrading?

Release notes summary

Reduces the database sanitization output limit to 48 characters, introduces a new `hash` template filter, and fixes interactive alias TTY handling alongside symlink resolution improvements.

Key Highlights

  • `sanitize_db` template filter caps output at 48 chars (was 63) to accommodate longer paths like Unix sockets.
  • New `hash` template filter produces a 3-character base36 digest of input.
  • Background hook announcements now collapse into a single line per command.
  • Picker-driven `post-switch` hooks now receive `target` and `target_worktree_path` template variables.
  • Fixed `wt <alias>` stdout piping and symlink-based worktree resolution.

Breaking Changes

  • Branches whose previous `sanitize_db` output exceeded 48 characters now generate different identifiers.

New Features

  • New `hash` template filter for custom truncate-with-collision-avoidance recipes.
  • Picker-driven `post-switch` hooks receive additional template variables.
  • Unified hook dispatch architecture for cleaner background hook announcements.

Full Release Notes

## Release Notes

### Improved

- **`sanitize_db` template filter caps output at 48 chars** (was 63 — PostgreSQL's identifier limit). The new budget leaves headroom for users composing the output into longer paths or identifiers, e.g., Unix socket paths capped at 107 bytes. The 3-character hash suffix is unchanged, so collision avoidance is preserved at the new budget; only the truncated base shrinks. (Breaking: branches whose previous `sanitize_db` output exceeded 48 chars get a different identifier — most names are well under 48 and pass through unchanged.) ([#2467](https://github.com/max-sixty/worktrunk/pull/2467), thanks @yajo for [#2397](https://github.com/max-sixty/worktrunk/issues/2397))

- **New `hash` template filter**: `{{ value | hash }}` produces a 3-character base36 digest of the input — useful for composing custom truncate-with-collision-avoidance recipes when `sanitize_db`'s 48-char budget still isn't tight enough. ([#2453](https://github.com/max-sixty/worktrunk/pull/2453))

- **Background hook announces collapse to one line per command**: `wt merge` (with removal) previously emitted two or three separate `◎ Running …` lines for `post-remove + post-switch` and `post-merge`. Hooks across all phases of a single command now share one combined announce: `◎ Running post-remove: user:cleanup; post-switch: user:notify; post-merge: user:sync`. ([#2457](https://github.com/max-sixty/worktrunk/pull/2457))

- **Picker-driven `post-switch` hooks now receive `target` / `target_worktree_path`** template variables, matching hooks fired from `wt switch <branch>`. Closes a pre-existing asymmetry where interactive switching exposed strictly fewer variables than the non-interactive path. ([#2470](https://github.com/max-sixty/worktrunk/pull/2470))

### Fixed

- **`wt <alias>` stdout is pipeable again**: `wt my-alias | tr …` (and any other downstream pipe) silently produced no output because the foreground executor redirected every alias body's stdout to wt's stderr — a hook-only redirect that PR [#2089](https://github.com/max-sixty/worktrunk/pull/2089) inherited uniformly. Aliases now pass stdout through; hooks and `wt step for-each` keep the merged-stderr behavior so their output stays ordered with wt's own status messages. ([#2479](https://github.com/max-sixty/worktrunk/pull/2479), thanks @davidmyersdev for reporting [#2478](https://github.com/max-sixty/worktrunk/issues/2478))

- **`wt switch <symlink>` resolves to the existing worktree** instead of failing with `No branch named …`. The path-based fallback in `Repository::worktree_at_path` previously compared paths via lexical normalization only, so symlink-equivalent spellings never matched. The same symlink-aware comparison is now used everywhere the library identifies a worktree by path. ([#2466](https://github.com/max-sixty/worktrunk/pull/2466))

- **Deleted-CWD recovery handles symlinked subdirectories**: When a worktree is removed while a shell sits in a subdirectory whose parents include a symlink (e.g. `~/link/repo.feature/src`), `wt` now finds the parent repository and recovers as expected. Previously the symlink-aware path compared only file names and bailed for any path deeper than the worktree root. ([#2464](https://github.com/max-sixty/worktrunk/pull/2464))

- **Merge safety backups for slash branches**: `WorkingTree::create_safety_backup()` flattened `/` to `-` in the ref path, so distinct branches like `a/b` and `a-b` collided at the same `refs/wt-backup/a-b` ref — the latest backup for one could clobber the other, and the documented `refs/wt-backup/<branch>` recovery path didn't match what was actually written. Slashes are valid in git ref names; the branch name is now used as-is. ([#2463](https://github.com/max-sixty/worktrunk/pull/2463))

- **Nushell wrapper migrates to function-level `@complete`**: Per fdncred's recommendation in [nushell/nushell#18128](https://github.com/nushell/nushell/issues/18128), the wrapper replaces the parameter-level `[...args: string@"nu-complete wt"]` with a function-level `@complete` attribute on an untyped `[...args]` rest, and the completer signature moves from `[context: string]` (with manual `split row " "` reconstruction) to `[spans: list<string>]`. Net deletion of seven lines of fragile token reassembly; once [nushell/nushell#18131](https://github.com/nushell/nushell/issues/18131) ships in stable nu the wrapper will automatically benefit from `--flag="value"` quote stripping and `~` expansion. ([#2458](https://github.com/max-sixty/worktrunk/pull/2458))

### Internal

- **Hook dispatch unified**: A single `HookAnnouncer` orchestrates per-command announces across phases, replacing scattered `CommandOrigin`-keyed dispatch with closures, and the various background-hook entry points now share one path. Switch and merge sites assemble template variables through a single `TemplateVars` builder. ([#2457](https://github.com/max-sixty/worktrunk/pull/2457), [#2470](https://github.com/max-sixty/worktrunk/pull/2470), [#2472](https://github.com/max-sixty/worktrunk/pull/2472), [#2477](https://github.com/max-sixty/worktrunk/pull/2477))

- **`wt list` rendering modes collapse**: The internal `RenderTarget` enum replaces three parallel rendering paths, the JSON path skips the per-result render that the formatter never used, and dead `is_tty` plumbing is removed. ([#2469](https://github.com/max-sixty/worktrunk/pull/2469), [#2473](https://github.com/max-sixty/worktrunk/pull/2473))

- **Push handling switches to a `PushKind` enum** instead of sniffing verb strings ([#2468](https://github.com/max-sixty/worktrunk/pull/2468)); `try_alias` and `step_alias` share one help-intercept implementation ([#2471](https://github.com/max-sixty/worktrunk/pull/2471)); lazy template expansion in the command executor lives in one place via `resolve_command_str` ([#2476](https://github.com/max-sixty/worktrunk/pull/2476)).

## Install worktrunk 0.46.0

### Install prebuilt binaries via shell script

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/max-sixty/worktrunk/releases/download/v0.46.0/worktrunk-installer.sh | sh && wt config shell install
```

### Install prebuilt binaries via powershell script

```sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/max-sixty/worktrunk/releases/download/v0.46.0/worktrunk-installer.ps1 | iex"; git-wt config shell install
```

### Install prebuilt binaries via Homebrew

```sh
brew install worktrunk && wt config shell install
```

## Download worktrunk 0.46.0

|  File  | Platform | Checksum |
|--------|----------|----------|
| [worktrunk-aarch64-apple-darwin.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.46.0/worktrunk-aarch64-apple-darwin.tar.xz) | Apple Silicon macOS | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.46.0/worktrunk-aarch64-apple-darwin.tar.xz.sha256) |
| [worktrunk-x86_64-apple-darwin.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.46.0/worktrunk-x86_64-apple-darwin.tar.xz) | Intel macOS | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.46.0/worktrunk-x86_64-apple-darwin.tar.xz.sha256) |
| [worktrunk-x86_64-pc-windows-msvc.zip](https://github.com/max-sixty/worktrunk/releases/download/v0.46.0/worktrunk-x86_64-pc-windows-msvc.zip) | x64 Windows | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.46.0/worktrunk-x86_64-pc-windows-msvc.zip.sha256) |
| [worktrunk-aarch64-unknown-linux-musl.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.46.0/worktrunk-aarch64-unknown-linux-musl.tar.xz) | ARM64 MUSL Linux | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.46.0/worktrunk-aarch64-unknown-linux-musl.tar.xz.sha256) |
| [worktrunk-x86_64-unknown-linux-musl.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.46.0/worktrunk-x86_64-unknown-linux-musl.tar.xz) | x64 MUSL Linux | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.46.0/worktrunk-x86_64-unknown-linux-musl.tar.xz.sha256) |



### Install via Cargo

```sh
cargo install worktrunk && wt config shell install
```

### Install via Winget (Windows)

```sh
winget install max-sixty.worktrunk && git-wt config shell install
```

### Install via AUR (Arch Linux)

```sh
paru worktrunk-bin && wt config shell install
```

Related Repositories