v0.72.0
max-sixty/worktrunkv0.72.0Aug 7, 2026by github-actions[bot]
Upgrading?
AI Summary
Improvements to forge classification, project configuration, and JSON schema, along with numerous fixes for shell integration, piping, and CI status parsing.
Key Highlights
- Hosts carrying forge names anywhere now resolve to the correct provider.
- Project configuration supports wildcard entries covering entire hosts.
- Approval state and branch-removal outcomes are now machine-readable (Breaking).
- Fixed piped output panics and ANSI handling.
- Fixed CI check status parsing for pending states.
Breaking Changes
- Fast-forward pushes in `wt merge` and `wt step push` no longer run `git push`.
- Approval state format changed from `branch_deleted` to `branch_outcome`.
New Features
- Published JSON Schema 2 for `wt list` output.
Full Release Notes
## Release Notes
### Improved
- **A host carrying a forge's name anywhere resolves to that forge again**: 0.71.0 required `github`, `gitlab`, or `gitea` as a whole dot-separated label, which read as an ownership check but wasn't one — an attacker controls their own DNS — while shutting out self-hosters with hyphenated names. `github-enterprise.acme.com`, `mygithub.com`, and the `github-personal` SSH alias classify again, so CI status, `wt switch --prs`, and `repo.provider` work with no config. ([#3673](https://github.com/max-sixty/worktrunk/pull/3673))
- **One `[projects."…"]` entry can cover every repository on a host, and can set the forge**: A key containing `*` matches any run of characters, `/` included, so `[projects."git.company.example/*"]` covers a whole host; every matching entry applies, least- to most-specific. The table also gained `forge.platform` and `forge.hostname`, so a self-hosted host needs one entry here rather than a block in every repo. [Docs](https://worktrunk.dev/config/#user-project-specific-settings) ([#3701](https://github.com/max-sixty/worktrunk/pull/3701), thanks @chrishas35 for the request and @witt-bit for the workspace-scoped case it partly serves)
- **`wt merge` and `wt step push` leave the target worktree's uncommitted changes in place**: The autostash that held a dirty target's changes restored them as unstaged; both now advance the target with a compare-and-swap `update-ref` and `read-tree -m -u`, which leaves uncommitted work untouched. (Breaking: the fast-forward path no longer runs `git push`, so `pre-push` and the receive-side hooks no longer fire, and a failed sync errors with the ref rolled back.) ([#3703](https://github.com/max-sixty/worktrunk/pull/3703), [#3684](https://github.com/max-sixty/worktrunk/pull/3684), [#3693](https://github.com/max-sixty/worktrunk/pull/3693), thanks @gubasso for reporting)
- **Approval state and branch-removal outcomes are machine-readable**: `wt config approvals list --format=json` reports whether a non-interactive run would stop for approval. `wt remove` and `wt step prune` replace `branch_deleted` with `branch_outcome`: `deleted`, `deferred`, `not_attempted`, `retained_unmerged`, `retained_checked_out`, `retained_raced`, `retained_failed`. (Breaking.) ([#3710](https://github.com/max-sixty/worktrunk/pull/3710), thanks @NathanaelRea for the requests)
- **Every commit hash worktrunk prints follows `core.abbrev`**: The `wt list` table, `wt switch --prs`'s `log` tab, and `wt config state`'s CI cache table sliced to 8 characters while `--format=json` carried git's `%h`. All now ask git how wide it abbreviates in this repo. ([#3676](https://github.com/max-sixty/worktrunk/pull/3676), [#3677](https://github.com/max-sixty/worktrunk/pull/3677))
- **`wt list --format=json` schema 2 has a published JSON Schema**: [worktrunk.dev/schema/list-v2.json](https://worktrunk.dev/schema/list-v2.json) holds the contract, and `wt list --print-schema` prints the same document. Four fields that were bare strings are now enumerated vocabularies; the emitted JSON is unchanged. ([#3747](https://github.com/max-sixty/worktrunk/pull/3747))
- **A detached worktree is named by its commit, not `-`**: The Branch cell hardcoded `-`, which reads as missing data rather than a state; it now carries the row's abbreviated HEAD, and the picker and statusline name the worktree the same way. ([#3675](https://github.com/max-sixty/worktrunk/pull/3675))
### Fixed
- **Piped output is plain and no longer panics**: `wt list | head -3` exited 101 with `failed printing to stdout: Broken pipe`, and `wt list` wrote ANSI to a pipe unconditionally. Every stdout surface now exits cleanly, and the human-read ones are plain when piped unless `CLICOLOR_FORCE=1`. ([#3746](https://github.com/max-sixty/worktrunk/pull/3746), [#3766](https://github.com/max-sixty/worktrunk/pull/3766))
- **A CI check that hasn't finished no longer reads as passed**: Each forge's status parser missed documented values, so a GitHub PR parked on an approval gate showed green, and GitLab's `canceling` and Azure DevOps's `postponed` read as no CI at all. ([#3741](https://github.com/max-sixty/worktrunk/pull/3741), [#3740](https://github.com/max-sixty/worktrunk/pull/3740))
- **The shell wrappers survive an `rm` alias and a failing `--execute`**: Aliases bake into the wrapper at parse time, so `alias rm='rm -v'` reached its cleanup — noise on zsh and bash, and on nushell an abort that leaked three temp files, as a failing `--execute` body also did. ([#3714](https://github.com/max-sixty/worktrunk/pull/3714), thanks @Ar4l), ([#3732](https://github.com/max-sixty/worktrunk/pull/3732), [#3734](https://github.com/max-sixty/worktrunk/pull/3734))
- **An alias or hook wrapping `wt switch` or `wt remove` keeps your subdirectory**: The user's position came from the `wt` process's cwd, which inside an alias body is the worktree root, so an aliased `wt remove` from `feature/apps/gateway` landed at the primary worktree's root. Fixes [#3723](https://github.com/max-sixty/worktrunk/issues/3723). ([#3724](https://github.com/max-sixty/worktrunk/pull/3724), thanks @vivienm for reporting)
- **`wt merge` and `wt step push` refuse a target worktree parked mid-operation**: The two-tree sync refuses an unmerged index but not a stopped cherry-pick or rebase whose conflict was already staged, so the push range could land in a paused target and be committed by `--continue`. ([#3759](https://github.com/max-sixty/worktrunk/pull/3759))
- **The Claude plugin's worktree-remove hook resolves against the worktree path**: The hook anchored at `CLAUDE_PROJECT_DIR`, which the `claude agents` view routinely leaves outside any repository, so `wt remove` died with `not a git repository` and the session became undeletable. Its guard now also requires a `.git` entry. ([#3754](https://github.com/max-sixty/worktrunk/pull/3754), [#3767](https://github.com/max-sixty/worktrunk/pull/3767), thanks @judewang for the fix and the report)
- **A Gitea API error is reported as one, not as a parse failure**: `tea api` exits 0 whatever the HTTP status, so both call sites guessed from the body's shape and blamed an API change for an API error. `--include` surfaces the status instead. ([#3713](https://github.com/max-sixty/worktrunk/pull/3713), [#3600](https://github.com/max-sixty/worktrunk/pull/3600))
- **`--print-schema` and the doc-generation help flags name the right command**: All three found the subcommand by scanning `argv` for a `/wt` suffix, which never matches `wt.exe` under a backslash path, so on Windows they read the binary's own path as the command. ([#3762](https://github.com/max-sixty/worktrunk/pull/3762))
- **A multibyte shell name no longer panics**: `extract_filename_from_path` sliced at `len() - 4` to test for `.exe` with no char-boundary check: `SHELL=/bin/日本語 wt config show` panicked, and on macOS every process name goes through it during shell detection. ([#3727](https://github.com/max-sixty/worktrunk/pull/3727))
- **`wt` installed under a dotted name generates shell integration for that name**: `binary_name` used `file_stem`, which cuts at the last dot, so `wt config shell init bash` under `wt.old` emitted a wrapper for `wt`. It now strips only the executable suffix. ([#3719](https://github.com/max-sixty/worktrunk/pull/3719))
- **Concurrent `wt step prune` removals no longer race the worktree registry**: `git worktree remove` reads every sibling under `.git/worktrees/`, so two overlapping removals could have one read a sibling mid-teardown. Registry-mutating removals now serialize behind a second lock. ([#3692](https://github.com/max-sixty/worktrunk/pull/3692))
- **The `wt switch` first-run offer previews the legacy files it removes**: Accepting "Install shell integration?" could delete a deprecated worktrunk-managed wrapper the prompt never named. What gets removed is unchanged. ([#3656](https://github.com/max-sixty/worktrunk/pull/3656))
- **`wt config create --project` writes a resolvable link**: The comment it writes into `.config/wt.toml` carried a raw Zola target, because the link-conversion regex stopped at the first `]` — here the one closing a nested code span. ([#3731](https://github.com/max-sixty/worktrunk/pull/3731))
- **`wt list --branches` counts a local branch containing `/` as local**: The summary tally classified branch-only rows by `branch.contains('/')`, so a local `feature/login` counted under "N remote branches". ([#3687](https://github.com/max-sixty/worktrunk/pull/3687))
- **`wt step relocate`'s human summary counts template-error branches as skipped**: `--format=json` already folded them into `skipped`; the human tally undercounted by the number of branches whose `worktree-path` template failed to expand. ([#3688](https://github.com/max-sixty/worktrunk/pull/3688))
### Documentation
- **SignPath attribution appears with the artifacts it describes**: The code-signing notice and a route to the policy now sit in the install section's Windows block on the README and the docs landing page, as SignPath Foundation's OSS program requires. ([#3709](https://github.com/max-sixty/worktrunk/pull/3709))
- **`wt step copy-ignored`'s `--require-include` example renders as a terminal block**: It was the only `console` block in the command's long help missing the `$ ` prefix. ([#3706](https://github.com/max-sixty/worktrunk/pull/3706))
### Internal
- **Library API rework** (Breaking library API): `cargo-semver-checks` fails five lints — `LegacyForgeAlias` and `Repository::legacy_forge_alias` removed with the forge-classification revert, `Repository::forge_platform_override` removed for one shared resolver, `stage_worktree_removal` gained two parameters, `UserProjectOverrides` gained a `forge` field, and the `real-repo-benches` feature was removed. ([#3673](https://github.com/max-sixty/worktrunk/pull/3673), [#3694](https://github.com/max-sixty/worktrunk/pull/3694), [#3701](https://github.com/max-sixty/worktrunk/pull/3701), [#3721](https://github.com/max-sixty/worktrunk/pull/3721))
- **One function owns the pre-removal gate**: `stage_worktree_removal` now performs the dirty-worktree gate, the fsmonitor stop, and the rename into trash for both removal paths. ([#3694](https://github.com/max-sixty/worktrunk/pull/3694))
- **The test mock stub is the `wt` binary itself**: `cargo test --test integration` never rebuilt the separate `mock-stub` package; `wt` is now linked under each mock's name instead. ([#3712](https://github.com/max-sixty/worktrunk/pull/3712))
- **`-vv` traces report a normal run's fork count**: `prewarm_at` fast-pathed on a cache key that `-vv`'s own logging setup had populated, so every trace overstated a normal run's config forks. ([#3705](https://github.com/max-sixty/worktrunk/pull/3705))
- **Benchmarks are organized by repository shape**: Groups select semantic `FixtureRecipe`s and share table-driven cases; separate fixtures remain only for a controlled contrast, a destructive precondition, or disproportionate setup cost. ([#3721](https://github.com/max-sixty/worktrunk/pull/3721))
- **`az` and `tea` spans group under `network` in `wt-perf timeline`**: The trace exporter's `network` bucket recognized only `gh` and `glab`. ([#3689](https://github.com/max-sixty/worktrunk/pull/3689))
## Install worktrunk 0.72.0
### Install prebuilt binaries via shell script
```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/max-sixty/worktrunk/releases/download/v0.72.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.72.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.72.0
| File | Platform | Checksum |
|--------|----------|----------|
| [worktrunk-aarch64-apple-darwin.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.72.0/worktrunk-aarch64-apple-darwin.tar.xz) | Apple Silicon macOS | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.72.0/worktrunk-aarch64-apple-darwin.tar.xz.sha256) |
| [worktrunk-x86_64-apple-darwin.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.72.0/worktrunk-x86_64-apple-darwin.tar.xz) | Intel macOS | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.72.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.72.0/worktrunk-x86_64-pc-windows-msvc.zip) | x64 Windows | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.72.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.72.0/worktrunk-aarch64-unknown-linux-musl.tar.xz) | ARM64 MUSL Linux | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.72.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.72.0/worktrunk-x86_64-unknown-linux-musl.tar.xz) | x64 MUSL Linux | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.72.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
A cross-platform desktop All-in-One assistant for Claude Code, Codex, OpenCode, OpenClaw, Grok Build & Hermes Agent. Only official website: ccswitch.io
An open-source remote desktop application designed for self-hosting, as an alternative to TeamViewer.
Build smaller, faster, and more secure desktop and mobile applications with a web frontend.
π RuView turns commodity WiFi signals into real-time spatial intelligence, vital sign monitoring, and presence detection — all without a single pixel of video.
Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one