v0.54.0

max-sixty/worktrunkv0.54.0May 26, 2026by github-actions[bot]

Upgrading?

AI Summary

This release focuses on performance and stability, introducing rate-limit pacing for Claude Code and streaming removals in `wt step prune`. It also migrates logging infrastructure and deprecates legacy shell-wrapper behavior.

Key Highlights

  • Rate-limit pacing segment in Claude Code statusline
  • `wt step prune` now streams removals and never prompts mid-scan
  • Default `--min-age` raised from 1h to 1d
  • Logging migration from `env_logger` to `tracing-subscriber`
  • `-vv` no longer floods stderr; raw subprocess sink renamed to `subprocess.log`

New Features

  • Streaming prune functionality
  • Rate-limit pacing visualization

Full Release Notes

## Release Notes

### Improved

- **Rate-limit pace segment in the Claude Code statusline**: `wt list statusline --format=claude-code` now surfaces a yellow `1.3×pace(10am–3pm)` segment when Claude Code's reported five-hour or seven-day rate-limit window is on track to be hit before its reset. The segment uses a Bayesian forecast on `P(final ≥ 100%)` so early-window bursts (e.g., 5% used at 3% elapsed) don't trigger spurious warnings — only the worse-projected of the two windows is shown, and the segment is hidden entirely when both are safe. The clock format inside the parentheses honors `LC_ALL` / `LC_TIME` / `LANG`: `en_US`/`en_PH`/`en_CA` get 12-hour (`10am–3pm`), everything else (including unset and `C`/`POSIX`) gets 24-hour (`10:00–15:00`). ([#2899](https://github.com/max-sixty/worktrunk/pull/2899), [#2911](https://github.com/max-sixty/worktrunk/pull/2911))

- **`wt step prune` streams removals and never prompts mid-scan**: Prune now bundles integration, removability, and age checks into a single parallel pass and starts removing candidates as soon as they qualify, instead of batching the scan and acting at the end. Dirty / locked / primary worktrees drop out before the age check, so a young + dirty + integrated worktree no longer surfaces as `Skipped (younger than 1d)` — the `(younger than X)` message now fires only when the worktree would actually have been pruned. With `--yes`, every project command is auto-approved as before; without `--yes`, a candidate whose hooks include an unapproved project command is SKIPPED with `(approval required)` rather than aborting the scan with an inline prompt the streaming structure couldn't accommodate. The end-of-run hint enumerates the unapproved hook templates from the invoking worktree's config and emits one copy-pasteable `wt -C <path> remove` line per skipped candidate, annotating candidates whose own `.config/wt.toml` differs from the invoking worktree's. ([#2908](https://github.com/max-sixty/worktrunk/pull/2908), [#2910](https://github.com/max-sixty/worktrunk/pull/2910))

- **`wt step prune` default `--min-age` raised from 1h to 1d**: A worktree just created from the default branch looks "merged" because its branch still points at the same commit; a one-day floor keeps an unattended prune from sweeping it up before its owner starts work. Explicit `--min-age=0s` or any other value is unchanged. ([#2886](https://github.com/max-sixty/worktrunk/pull/2886))

- **Legacy shell-wrapper deprecation warning**: Users who upgrade `wt` without restarting their shell still run the previous release's wrapper, which sets only `WORKTRUNK_DIRECTIVE_FILE` instead of the new split `WORKTRUNK_DIRECTIVE_CD_FILE` / `WORKTRUNK_DIRECTIVE_EXEC_FILE` pair. That fallback used to be silent; `wt` now emits a one-shot per-process warning hinting at `wt config shell install`. bash, zsh, fish, and PowerShell pick up the new wrapper on the next shell restart; nushell is the one shell where users must rerun `wt config shell install nu` because its wrapper is a static file. ([#2880](https://github.com/max-sixty/worktrunk/pull/2880))

- **`-vv` no longer floods stderr; raw subprocess sink renamed to `subprocess.log`**: A `-vv` invocation used to spray ~15K lines of stderr per command, forcing a redirect to a file even though `trace.log` already mirrored most of it. The debug-level `log::*` pipeline now writes to `.git/wt/logs/trace.log` at `-vv`; Info-level records (hook output, template expansions, the `Tracing to …` pointer) stay on stderr at every verbosity level. The companion raw-subprocess-bytes file is renamed from `output.log` to `subprocess.log` — the prior name read as "stuff `wt` printed" but actually held uncapped multi-MB subprocess bodies (`git log -p`, patch-id pipelines). `RUST_LOG` is now honored at every verbosity level: `wt -v` and `wt -vv` previously hardcoded Info / Debug and silently dropped any `RUST_LOG` directive (`RUST_LOG=trace wt -vv` was capped at Debug, `RUST_LOG=worktrunk=trace wt -v` was ignored); all three levels now flow through one builder shape with `RUST_LOG` layered on top of the flag baseline. ([#2892](https://github.com/max-sixty/worktrunk/pull/2892), [#2901](https://github.com/max-sixty/worktrunk/pull/2901), [#2913](https://github.com/max-sixty/worktrunk/pull/2913))

### Fixed

- **Data-safety across the worktree merge / remove / prune lifecycle**: Five TOCTOU and scoping fixes. `wt merge` and `wt remove` revalidate cleanliness and branch integration *after* `pre-remove` hooks run, so a hook (or concurrent process) that dirties the worktree or advances the branch can no longer trash the directory or trigger `git branch -D` against the stale pre-hook decision. The background-removal path does the same revalidation and fails closed for submodule worktrees on the fallback path instead of forcing. `wt step prune` runs its rename-failure fallback `git branch -d` synchronously under the write guard for non-current worktrees (no more race against live integration readers on `.git/config`), scopes hook approval to the worktrees it will actually remove (an unrelated unapproved hook can no longer abort a non-interactive prune), and prunes the stale metadata of detached worktrees whose directory was deleted outside Worktrunk. ([#2870](https://github.com/max-sixty/worktrunk/pull/2870))

### Documentation

- **`-v` / `-vv` help and FAQ**: The `-v` help text is split out from its 150-character parenthetical, and `docs/content/faq.md` gains a "What does `-v` / `-vv` do?" section with a three-level table. ([#2913](https://github.com/max-sixty/worktrunk/pull/2913))

- **Hook docs and `pre-start` docstring**: The `pre_create` field docstring in `HooksConfig` (user-visible via generated JSON schema) read "Commands to execute before worktree creation"; `pre-start` actually runs *after* worktree creation, blocking. Restored the correct wording. The `docs/content/extending.md` overview is reworked to read more cleanly: parallel three-paragraph intro for hooks / aliases / custom subcommands, fewer em-dashes, and the Reference section's table no longer duplicates content covered in prose. ([#2879](https://github.com/max-sixty/worktrunk/pull/2879), [#2912](https://github.com/max-sixty/worktrunk/pull/2912))

### Internal

- **Migrated logging from `env_logger` to `tracing-subscriber`**: A layered subscriber routes records structurally by target and verbosity. Existing `log::*` callers are bridged into `tracing` via `tracing_log::LogTracer`. `[wt-trace]` records emit as typed structured fields with a single formatter rendering the wire shape, so the grammar lives in one place instead of being duplicated across every emit site.

- **`UncommittedChanges` error renders dirty files in the canonical gutter**, matching `ConflictingChanges` and the project's `format_with_gutter()` convention. ([#2887](https://github.com/max-sixty/worktrunk/pull/2887))

## Install worktrunk 0.54.0

### Install prebuilt binaries via shell script

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/max-sixty/worktrunk/releases/download/v0.54.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.54.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.54.0

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