Skip to content

Release v0.78.0 - #4138

Merged
max-sixty merged 16 commits into
mainfrom
release
Sep 16, 2026
Merged

max-sixty merged 16 commits into
mainfrom
release

Conversation

@max-sixty

@max-sixty max-sixty commented Sep 16, 2026 •

Copy link
Copy Markdown
Owner

Version bump to 0.78.0 plus the changelog section for this release, a comment-preservation fix the release review turned up, a PTY test-filter fix, and merges of main (commits that landed during the review, including #4124, #4122, #4141, #4140 and #4145).

Bump level: minor. cargo semver-checks reports 2 breaking library changes — a new leftover_branch field on the GitError::WorktreeCreationFailed struct variant, and DEPRECATED_TEMPLATE_VARS removed. Pre-1.0, so a breaking change takes a minor bump.

Comment-preservation fix

A config save — declining the commit-generation offer, say — dropped the trailing comment on every value it rewrote. That was already true in v0.77.0 for a line the save itself changed (skip-commit-generation-prompt = false # note → true, reproduced on the v0.77.0 binary). #4080 widened it to lines the command never touched: retired template variables now migrate on every load, so the next unrelated save rewrote any line naming one. #4080's own PR said this "needs a call before merge" and merged without one.

  • replace_keeping_decor keeps the value's decor (spacing after =, trailing comment) when merge_tables replaces a scalar. Comments above a line sit on the key and were never at risk.
  • main's fix(config): preserve unknown keys and comments when rewriting an inline config section #4120 independently reworked the inline-table arm and moved a key's leading comment onto the rewritten header, but documented that a trailing comment after the closing brace "is still dropped". replace_inline_with_table now carries it after the header's ], so both of its callers keep it.
  • Tests: the existing comment-preservation test goes back to a retired name (the only fixture that exercises the replacement) and adds a comment on the mutated line; a new test snapshots and re-parses an inline [projects] entry. Each fails without its half of the fix.

Not fixed, and identical on v0.77.0: a save deletes an explicitly written default value (e.g. skip-commit-generation-prompt = false # note) as a stale key, comment included.

--help correction

#4104 moved the OpenCode plugin path to etcetera's Xdg strategy, which uses $XDG_CONFIG_HOME only when absolute — a deliberate, documented choice — but wt config plugins opencode --help still said install mirrors OpenCode's precedence. OpenCode reads a relative value as-is, so the help now says an empty $OPENCODE_CONFIG_DIR and an empty or relative $XDG_CONFIG_HOME are ignored.

Data-loss surface review

Independent finders swept the full diff, including the five commits merged from main. Nothing holds the tag; candidates were adjudicated against v0.77.0 binaries:

Changelog

37 entries; each new or edited entry was re-verified against v0.77.0 until a pass came back clean (six passes in all). Passes caught, among others: the OpenCode -x recipe was wrong since 0.76.0, not 0.75.0; #4080's JSON reaches every hook, not just pre-start/post-start; a quoted git error string git never prints; a copy-button overlap that existed only inside this release; #4104's relative-XDG_CONFIG_HOME OpenCode change described as a fix when OpenCode reads that path as-is; and which commands a broken inline section stopped. Fixed leads with the data-loss entries.

Open PRs checked against v0.77.0

PTY filter fix

test (macos) failed once on a shell's failed-setpgid diagnostic that landed straight after the capture's trailing \x1b[0m, with no newline between, so #4136's line-anchored filter missed it. The filter now also matches right after an SGR escape and keeps the escape; its test gains that shape and fails on the old pattern.

Validation

This was written by Claude Code on behalf of max-sixty

🤖 Generated with Claude Code

max-sixty and others added 3 commits September 15, 2026 23:44
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`merge_tables` replaced a changed item with `*existing_item =
desired_item.clone()`, and `toml_edit` carries decor on the value, so the
line lost whatever the user wrote around it.

That was latent until #4080 moved the retired template variables from
`UpdateOnly` to `Structural`. The rewrite now runs on every load, so the
in-memory config differs from the file at any line naming a retired
variable, and the next unrelated mutation — declining the
commit-generation offer, say — diffs the migrated value against the file
and replaces that line. A user with

    worktree-path = "../{{ main_worktree }}.{{ branch }}"  # note

lost the comment to a command that had nothing to do with it, which is
the implicit destructive side effect CLAUDE.md's Data Safety section
rules out.

Both replacing arms route through `replace_keeping_decor`, which moves
the existing value's decor across and lets the destination place it: on
the value for a scalar or inline table, on the header for a standard
table, since an inline table replaced by a standard one turns one line
into a section. The recursing arm needs nothing — it edits through the
existing table and never replaces it.

#4080's PR flagged the scalar case as needing a call before merge and
merged without one. The regression is unreleased, so there is no
changelog entry — a 0.77.0 reader never had the broken behavior.

`test_saving_config_mutation_preserves_toml_comments` goes back to a
retired name, which is what exercises the replacement at all, and gains a
comment on the line the mutation itself changes plus an assertion that
the migration still happens. `..._preserves_an_inline_entry_comment`
covers the inline `[projects]` shape, where the replacement changes the
item's kind. Both fail without this change; the scalar one passes at
v0.77.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolves the conflict in src/config/user/persistence.rs, where #4120 and
the release branch's comment-preservation fix both edited merge_tables.

#4120's inline-table arm is taken whole: it merges through the recursive
path so nested preserve applies, and moves the key's leading decor onto
the header through replace_inline_with_table. It left two gaps, one by
design ("the value's own decor ... is still dropped"):

- The scalar arm still replaced a changed value wholesale, dropping the
  line's comment. replace_keeping_decor from the release branch is kept
  for that arm.
- A trailing comment after an inline table's closing brace was
  discarded by InlineTable::into_table. replace_inline_with_table now
  reads it from the still-inline value and sets it after the header's
  `]`, so both of its callers (the save path and
  ensure_standard_table_parent) keep it.

test_saving_config_mutation_preserves_an_inline_entry_comment now
snapshots the rewritten file and parses it, since #4120's point was that
a misplaced comment is what breaks the header.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things left after the base merge — the changelog, and one docstring line.
The merge's resolution already closes what I'd have raised on
replace_keeping_decor: the header indent and dropped blank-line separator the
release-branch version produced are gone now that replace_inline_with_table
owns the inline→standard case, and the new snapshot pins the result.

The changelog doesn't cover what the base merge made reachable. Four
user-facing commits are now ancestors of the release commit, and none has an
entry:

  • #4135 feat(config)!: split the Pi and oh-my-pi plugin installers — breaking; wt config plugins pi stops writing the oh-my-pi hook
  • #4120 fix(config): preserve unknown keys and comments when rewriting an inline config section
  • #4126 fix(config): report a failed migration diff instead of rendering none
  • #4104 fix: ignore an empty or relative XDG_*_HOME across every XDG base-directory read

release.yaml builds the release notes from the tree at the tag, so these ship
undocumented unless they're folded in first. Drafts suggested inline; I wrote
them from each PR's description rather than its full diff, so they still want
the release skill's own verification pass. #4104 may read better combined with
the existing #4085 and #4107 entries — all three are the same "non-absolute env
var resolved against the wrong directory" shape.

The release-branch fix belongs in that #4120 entry. The PR body drops it
because "a 0.77.0 reader never had the broken behavior", which holds for the
unrelated-line amplification #4080 introduced but not for the mutated line
itself: merge_tables's scalar arm is byte-identical at v0.77.0, so any key
whose value a save changed lost its trailing comment there too. That is what
this PR's own # comment on the mutated line assertion covers — reverting just
the replace_keeping_decor call reproduces it. The reachable 0.77.0 paths are
narrow (skip-commit-generation-prompt = false # … flipping to true, a
project entry whose worktree-path is replaced), so whether it earns its own
words is a judgment call; the stated reason for omitting it is what doesn't
hold.

Not approving. src/commands/step/prune.rs carries the --min-age guard that
decides whether wt step prune deletes a branch with no worktree —
orphan_branch_age is what CheckSource::Orphan feeds into the RemovalPlan
age gate — and the FAQ edit extends "What can Worktrunk delete?". running-tend's
references/review-pr.md, under "Data-Loss Surface: Hold for Human Review",
holds both for a human either way. It also says to request review from
@max-sixty, which is moot here since you're the author.

Comment thread CHANGELOG.md
Comment thread CHANGELOG.md
Comment thread src/config/user/persistence.rs Outdated
max-sixty and others added 4 commits September 16, 2026 10:28
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comments above a line sit on the key's leaf decor, which a value
replacement never touches; the value's decor is the spacing after `=` and
the trailing comment. The docstring also named only the migration trigger,
but a save that changes a value dropped its trailing comment in v0.77.0 too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#4104 moved the OpenCode plugin path to etcetera's Xdg strategy, which
uses $XDG_CONFIG_HOME only when it is absolute, but the help text still
said install mirrors OpenCode's precedence. OpenCode reads a relative
value as-is, so the two differ there; say that an empty
$OPENCODE_CONFIG_DIR and an empty or relative $XDG_CONFIG_HOME are
ignored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One user-visible move in this release still has no changelog entry: where the
OpenCode plugin is written under a relative $XDG_CONFIG_HOME.

At v0.77.0, opencode_plugins_dir took a non-empty $XDG_CONFIG_HOME at
face value — PathBuf::from(xdg).join("opencode") — so XDG_CONFIG_HOME=cfg
put the plugin at <invocation dir>/cfg/opencode/plugins/worktrunk.ts. #4104
replaced that arm with Xdg::config_dir(), so 0.78.0 writes
~/.config/opencode/… instead. Install, uninstall, and the wt config show
status line all resolve through plugin_path(), so a plugin an older wt left
at the relative path is now neither reported as outdated nor removable by
wt config plugins opencode uninstall.

The PR body says a verification pass rejected calling this a fix, because
OpenCode reads a relative value as-is. That holds for the framing, but the path
moved either way, and the identical empty-OPENCODE_CONFIG_DIR case one rung
up carries its own entry (#4084). Suggested as a clause on the #4104 entry,
since it's the same commit.

Everything else in the four new commits checks out against the code it
describes. The data-loss hold from the previous review is unchanged.

Comment thread CHANGELOG.md

- **`wt config shell install zsh` honours `$ZDOTDIR` only when it is absolute**: an empty or relative value resolved against the current directory, so install appended to a `.zshrc` under the current directory and reported success while the file zsh reads went untouched. A non-absolute value now falls back to `$HOME`. ([#4085](https://github.com/max-sixty/worktrunk/pull/4085))

- **`wt config shell install` for Nushell no longer deletes a `wt.nu` under the current directory**: an empty or relative `$XDG_CONFIG_HOME` resolved against it, so install removed a `nushell/vendor/autoload/wt.nu` beneath it unread, as a stale wrapper. It now falls back to `~/.config`, which also fixes unread `tea` logins that let `wt switch pr:<n>` treat a Gitea host as GitHub. ([#4104](https://github.com/max-sixty/worktrunk/pull/4104))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A relative $XDG_CONFIG_HOME moved the OpenCode plugin's path too, and plugin_path() is what install, uninstall, and the status line all read — so the old file is stranded rather than migrated. Wording is a starting point; the release skill's verification pass should still take it.

Suggested change
- **`wt config shell install` for Nushell no longer deletes a `wt.nu` under the current directory**: an empty or relative `$XDG_CONFIG_HOME` resolved against it, so install removed a `nushell/vendor/autoload/wt.nu` beneath it unread, as a stale wrapper. It now falls back to `~/.config`, which also fixes unread `tea` logins that let `wt switch pr:<n>` treat a Gitea host as GitHub. ([#4104](https://github.com/max-sixty/worktrunk/pull/4104))
- **`wt config shell install` for Nushell no longer deletes a `wt.nu` under the current directory**: an empty or relative `$XDG_CONFIG_HOME` resolved against it, so install removed a `nushell/vendor/autoload/wt.nu` beneath it unread, as a stale wrapper. It now falls back to `~/.config`, which also fixes unread `tea` logins that let `wt switch pr:<n>` treat a Gitea host as GitHub, and moves the OpenCode plugin off that same relative path — reinstall with `wt config plugins opencode install`, and delete any `opencode/plugins/worktrunk.ts` an earlier version left there. ([#4104](https://github.com/max-sixty/worktrunk/pull/4104))

max-sixty and others added 4 commits September 16, 2026 10:52
Brings in #4124 (template-variable scanner), #4122 (deprecated keys in an
inline [projects] scope), #4136 (PTY snapshot diagnostic), and #4134 (tend
workflows).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit spliced the rewrite at offsets computed before an
earlier replacement in the same file, so the entry's opening and link
were mangled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new #4124 entry scopes the bug to wt config update, but the rewrite it
fixes runs on every config load.
migrate_template_vars_doc is a
DeprecationRule::Structural row, and its comment in
src/config/deprecation.rs says why — "the load-path rewrite is what keeps an
unmigrated template rendering what it always did". So a hook, alias, or
worktree-path template hitting either shape rendered wrong on every wt
command; wt config update only writes the same rewrite into the file. As
written, a reader who never runs wt config update reads the entry as "not
me". The #4122 entry added alongside it gets this right — "now migrate", no
command named — and merge.no-ff sits on the same Structural load path.

The entry also stops at "left unmigrated, for hand-editing". No renderer
supplies the retired names any more, so a template that binds one now fails its
SemiStrict expansion — or renders nothing, in a squash template — and, per
template_bound_names, raises no deprecation warning naming the cause. A
0.77.0 config that worked breaks on upgrade with nothing pointing at why, which
is worth the clause. Suggested wording covers both halves; the release skill's
verification pass should still take it.

Comment thread CHANGELOG.md Outdated
- **`wt config plugins opencode install` no longer writes the plugin into the current directory**: with `OPENCODE_CONFIG_DIR` set but empty, it wrote `plugins/worktrunk.ts` there, typically inside a repository, reporting success though OpenCode never saw it. An empty value now counts as unset. ([#4084](https://github.com/max-sixty/worktrunk/pull/4084))
- **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 reports `unknown field select (will be ignored)`. ([#4121](https://github.com/max-sixty/worktrunk/pull/4121), thanks @zach-hammad-vs for reporting)

- **`wt config update` no longer changes what a template renders when renaming a retired variable**: it rewrote `{{ repo_root }}` inside a quoted string, and turned `{{ repo_root }}{% set repo_root = 'x' %}{{ repo_root }}` into one printing the path twice. A template that binds a retired name or its replacement is now left unmigrated, for hand-editing. ([#4124](https://github.com/max-sixty/worktrunk/pull/4124), thanks @zach-hammad-vs for reporting)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **`wt config update` no longer changes what a template renders when renaming a retired variable**: it rewrote `{{ repo_root }}` inside a quoted string, and turned `{{ repo_root }}{% set repo_root = 'x' %}{{ repo_root }}` into one printing the path twice. A template that binds a retired name or its replacement is now left unmigrated, for hand-editing. ([#4124](https://github.com/max-sixty/worktrunk/pull/4124), thanks @zach-hammad-vs for reporting)
- **Renaming a retired template variable no longer changes what the template renders**: the rename runs on every config load, where it rewrote `{{ repo_root }}` inside a quoted string and turned `{{ repo_root }}{% set repo_root = 'x' %}{{ repo_root }}` into one printing the path twice. A template that binds a retired name or its replacement is now left unmigrated and no longer warns, so it fails expansion — or renders nothing, in a squash template — until the name is hand-edited. ([#4124](https://github.com/max-sixty/worktrunk/pull/4124), thanks @zach-hammad-vs for reporting)

The #4136 filter matched the diagnostic only at a line start. On macOS CI
(actions/runs/35132033122) bash wrote it straight after the capture's
trailing `\x1b[0m`, with no newline between, so
test_bash_shell_integration_hint_suppressed failed on a snapshot diff.
The filter now also matches right after an SGR escape and keeps the
escape. The filter test gains that shape, and fails on the old pattern.

Also, in the changelog, note that a #4124 template binding a retired
name gets no deprecation warning.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rewritten #4124 entry promises a loud failure where the squash-template case is silent. deprecated_vars_in_template keeps a pair only when used_vars.contains(*old) && !bound.contains(old) && !bound.contains(new), so binding the canonical name drops it too: {% for commit_details in commits %} in a squash template leaves commits retired. build_prompt renders under minijinja's default UndefinedBehavior::Lenient, which iterates an undefined value as an empty sequence, so that prompt loses its commit list with no error and no deprecation warning — the RETIRED_VARS and template_bound_names docstrings in src/config/deprecation.rs both spell this out, the latter naming that exact shape. "A template that binds a retired name … fails to render until edited" covers neither the binding shape (this commit dropped the previous wording's "or its replacement") nor the outcome, so the user whose commit prompt silently emptied reads the entry as not theirs.

Suggested inline. I left the sentence's lead as written — the still-open thread on this entry covers that half.

Not approving: the data-loss hold from the first review is unchanged.

Comment thread CHANGELOG.md Outdated
max-sixty and others added 3 commits September 16, 2026 12:17
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings in #4141 (template variables migrate through MiniJinja's parser),
#4140 (refuse to write a config rewrite that is not valid TOML), #4145
(report ignored project-config keys from every worktree), #4139, #4137.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The head merge put #4145 on the release branch with no changelog entry. be39a71e drops the is_main_worktree gate on ProjectConfig::load's unknown-field warning, so a project-config key that is ignored at runtime now says so from any worktree. Before it, the message fired only from the primary worktree — which is not where wt merge runs, and in a bare repo is nowhere at all — while wt config show warned from anywhere through collect_unknown_warnings, so the two channels disagreed about the same file. It closes a user report (#4144), and release.yaml builds the notes from the tree at the tag, so as it stands 0.78.0 ships it undocumented. Suggested inline; placement next to the other warning entry is a guess, and the release skill's verification pass should still take the wording.

The merge's other two commits need no entry: #4141 is the scanner-to-parser refactor, whose own message records that the shadowing policy is unchanged, and #4139 is comments only.

Not approving: the data-loss hold from the first review is unchanged.

Comment thread CHANGELOG.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@max-sixty
max-sixty merged commit bd49d14 into main Sep 16, 2026
41 checks passed
@max-sixty
max-sixty deleted the release branch September 16, 2026 20:45
social4hyq pushed a commit to social4hyq/homebrew-core that referenced this pull request Sep 20, 2026
worktrunk 0.78.0

Created-by: HarmonybrewBot
Commit-by: HarmonybrewBot
Merged-by: HarmonybrewBot
Description: Created by `brew bump`

---

Created with `brew bump-formula-pr`.<details>
  <summary>release notes</summary>
  <pre>## Release Notes

### Improved

- **Hook scripts read renamed context keys**: the JSON piped to every hook drops `worktree`, `repo_root`, `main_worktree`, and `main_worktree_path` in favour of `worktree_path`, `repo_path`, `repo`, and `primary_worktree_path`. Config templates migrate on load, so hooks written with the old names keep working; scripts reading the JSON, `--execute` templates, and `--var` overrides need updating. (Breaking.) ([#4080](max-sixty/worktrunk#4080))

- **`wt config plugins pi` installs for Pi; oh-my-pi moves to `wt config plugins omp`**: `pi install` wrote 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 until `wt config plugins omp install`. (Breaking: scripts that run `wt config plugins pi` for oh-my-pi need `omp`.) [Docs](https://worktrunk.dev/claude-code/#oh-my-pi) ([#4135](max-sixty/worktrunk#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 in `wt merge` began with one. ([#4059](max-sixty/worktrunk#4059))

- **`wt config show` gives a single `wt config shell install` hint**: an outdated wrapper, a fish wrapper at the deprecated `conf.d` path, and missing fish completions each printed their own hint. The zsh `compinit` snippet is now syntax-highlighted. ([#4059](max-sixty/worktrunk#4059))

- **`wt config update --output <path>` confirms the write**: it now prints `✓ Wrote user config migration @ ~/migrated.toml` where it printed nothing. `--output=-` stays silent. ([#4053](max-sixty/worktrunk#4053))

### Fixed

- **Removal no longer discards untracked files hidden by `status.showUntrackedFiles = no`**: under that setting, a worktree holding only untracked files looked clean, so `wt remove`, `wt merge`, and `wt step prune` removed it without `--force`. Safety checks now always count untracked files, and auto-staging warnings list every file they stage. ([#4111](max-sixty/worktrunk#4111), thanks @Duang777)

- **`wt remove` and `wt merge` check the right worktree under an inherited `GIT_DIR`**: run from a `!wt` git alias or a git hook, which pass `GIT_DIR` down, `wt remove` could delete a dirty worktree without `--force`, and `wt merge` could refuse a clean merge. Both now check the worktree they act on. ([#4082](max-sixty/worktrunk#4082), thanks @Duang777)

- **`wt merge` no longer deletes a worktree locked with `git worktree lock`**: the merge completes and prints `Worktree preserved (locked)`. Every Worktrunk removal refuses a locked worktree, even with `--force`; run `git worktree unlock <path>` first. ([#4073](max-sixty/worktrunk#4073), thanks @Duang777)

- **`wt config update` no 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](max-sixty/worktrunk#4127), thanks @zach-hammad-vs for reporting)

- **`wt step prune` no longer deletes a branch created minutes ago from an older commit**: `--min-age` aged 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](max-sixty/worktrunk#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 several `wt step` commands then failed with `Failed to load config`; `wt list` warned and ran without the config. Saves also dropped unrecognized keys from inline sections. ([#4120](max-sixty/worktrunk#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](max-sixty/worktrunk#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, so `wt merge` from a linked worktree ignored it silently. ([#4145](max-sixty/worktrunk#4145), thanks @ee-prog for reporting)

- **`wt merge` skips `post-commit` when 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 to `pre-remove`. ([#4049](max-sixty/worktrunk#4049))

- **`wt switch` recovers from a removed worktree in a bare repository**: `wt switch` and the picker surfaced git's raw `fatal: Unable to read current working directory` instead, and the removed-directory message lacked `wt switch ^`. ([#4067](max-sixty/worktrunk#4067))

- **`wt config shell install fish` puts the wrapper where fish reads it under a custom `$XDG_CONFIG_HOME`**: the wrapper always went to `~/.config/fish/functions/`, so install reported success while `wt switch` changed no directory. On Windows, the fish completion moves from `%APPDATA%\fish\completions` to the wrapper's directory. ([#4107](max-sixty/worktrunk#4107))

- **`wt config shell install zsh` honours `$ZDOTDIR` only when it is absolute**: an empty or relative value resolved against the current directory, so install appended to a `.zshrc` under the current directory and reported success while the file zsh reads went untouched. A non-absolute value now falls back to `$HOME`. ([#4085](max-sixty/worktrunk#4085))

- **`wt config shell install` for Nushell no longer deletes a `wt.nu` under the current directory**: an empty or relative `$XDG_CONFIG_HOME` resolved against it, so install removed a `nushell/vendor/autoload/wt.nu` beneath it unread, as a stale wrapper. It now falls back to `~/.config`, which also fixes unread `tea` logins that let `wt switch pr:<n>` treat a Gitea host as GitHub. ([#4104](max-sixty/worktrunk#4104))

- **`wt switch --create` names the branch a failed worktree add leaves behind**: `git worktree add -b` writes 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](max-sixty/worktrunk#4109), thanks @technicalpickles for reporting)

- **`wt config shell install` no 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](max-sixty/worktrunk#4059))

- **`wt config shell install` migrates a deprecated `conf.d` fish wrapper when `~/.config/fish/functions` is missing**: before, only `wt config shell install fish` migrated it. `wt config show` now reports that wrapper even when fish isn't on `PATH`. ([#4059](max-sixty/worktrunk#4059))

- **Plugin status and uninstall checks ask the agent CLIs**: `wt config show` could list an installed Claude Code plugin or Gemini CLI extension as not installed, and `wt config plugins claude|codex uninstall` could report success over a marketplace still configured. A repeat Claude uninstall now runs both removals instead of stopping at `Plugin not installed`. ([#4048](max-sixty/worktrunk#4048), [#4054](max-sixty/worktrunk#4054))

- **`wt config plugins opencode install` no longer writes the plugin into the current directory**: with `OPENCODE_CONFIG_DIR` set but empty, it wrote `plugins/worktrunk.ts` there, typically inside a repository, reporting success though OpenCode never saw it. An empty value now counts as unset. ([#4084](max-sixty/worktrunk#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 `💬` and `wt list` showed it as active until `wt config state marker clear` was run by hand. ([#4129](max-sixty/worktrunk#4129), thanks @drewdas for reporting)

- **`wt config update` migrates `[ci] platform` into a `[forge]` section that only sets `hostname`**: that config, common with GitHub Enterprise and self-hosted GitLab, kept the deprecated key with no deprecation warning. ([#4061](max-sixty/worktrunk#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](max-sixty/worktrunk#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 reports `unknown field select (will be ignored)`. ([#4121](max-sixty/worktrunk#4121), thanks @zach-hammad-vs for reporting)

- **`wt config update` no longer changes what a template renders**: it could rename `{{ repo_root }}` inside a quoted s

See merge request: Harmonybrew/homebrew-core!20435
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants