Repository navigation
Conversation
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
left a comment
There was a problem hiding this comment.
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 pistops 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.
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
left a comment
There was a problem hiding this comment.
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.
|
|
||
| - **`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)) |
There was a problem hiding this comment.
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.
| - **`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)) |
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
left a comment
There was a problem hiding this comment.
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.
| - **`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) |
There was a problem hiding this comment.
| - **`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
left a comment
There was a problem hiding this comment.
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.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
worktrunk-bot
left a comment
There was a problem hiding this comment.
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.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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
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-checksreports 2 breaking library changes — a newleftover_branchfield on theGitError::WorktreeCreationFailedstruct variant, andDEPRECATED_TEMPLATE_VARSremoved. 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_decorkeeps the value's decor (spacing after=, trailing comment) whenmerge_tablesreplaces 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_tablenow carries it after the header's], so both of its callers keep it.[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.--helpcorrection#4104 moved the OpenCode plugin path to etcetera's
Xdgstrategy, which uses$XDG_CONFIG_HOMEonly when absolute — a deliberate, documented choice — butwt config plugins opencode --helpstill said install mirrors OpenCode's precedence. OpenCode reads a relative value as-is, so the help now says an empty$OPENCODE_CONFIG_DIRand an empty or relative$XDG_CONFIG_HOMEare 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:wt config shell install(with Nushell) deleting anushell/vendor/autoload/wt.nuunder the current directory when$XDG_CONFIG_HOMEis empty or relative; fix(config): preserve unknown keys and comments when rewriting an inline config section #4120 stops a save or migration from writing an inline section's comment inside[commit]brackets, which made the config unparseable, and from dropping unknown keys.wt step prune --min-agedoesn't apply where no reflog exists — pre-existing, proven by building the pre-Age branches by reflog entry in wt step prune; fix four doc claims #4077 binary.git clone --bareandgit fetchintorefs/heads/*write no reflog, so in the bare layouttips-patterns.mdrecommends every branch starts unprotected. Docstring corrected here; behavioral fix is follow-up.wt config update --output <path>replaces any named file whole, no existence check or prompt — pre-existing, follow-up.wt config shell install fish --yesreplaces a hand-written wrapper at the XDG path — accepted ("ownership is the path", predates fix(shell): reclaim the legacy wrapper paths instead of inspecting them #3602), newly reachable only for users with$XDG_CONFIG_HOMEset. The FAQ's delete inventory now cross-references it.--var <retired-name>binds a name nothing references after refactor(config): stop supplying the deprecated template variables, migrating them on load #4080 — inert, no shipped usage; follow-up.pi uninstallpoints atomp uninstall.PI_CODING_AGENT_DIR='~/x'is used literally (Pi expands~) — creates a stray directory, deletes nothing; follow-up.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
-xrecipe was wrong since 0.76.0, not 0.75.0; #4080's JSON reaches every hook, not justpre-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_HOMEOpenCode 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
Nothing to commitrefusal understatus.showUntrackedFiles = no). Not merged, by decision.PTY filter fix
test (macos)failed once on a shell's failed-setpgiddiagnostic 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
cargo run -- hook pre-merge --yes— 4930 tests passed, 1 skipped; clippy, fmt, doc-sync, snapshot and lockfile checks greenfull-testson linux/macos/windows,feature-powerset, allrelease-targets,nix-flake,minimal-versions,link-check🤖 Generated with Claude Code