Skip to content

fix(switch): track a remote base only when the names match - #3913

Merged
max-sixty merged 4 commits into
mainfrom
docs/issue-3912-remote-base-upstream
Aug 28, 2026
Merged

max-sixty merged 4 commits into
mainfrom
docs/issue-3912-remote-base-upstream

Conversation

@worktrunk-bot

@worktrunk-bot worktrunk-bot commented Aug 25, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

wt switch --create <name> --base origin/<branch> ran git branch --unset-upstream afterwards, so the new branch could not push to the base (#713). Nothing said so, and #3912 was the third thread to arrive at the manual git push --set-upstream origin <branch> step. Reviewing the docs for it (@max-sixty, comment) surfaced the better question: should this just copy git?

Solution

Git already ships the rule the unset was approximating — branch.autoSetupMerge = simple sets tracking only when the start point is a remote-tracking branch and the new branch has the same name as the remote branch. wt now defaults to it on the --create paths instead of undoing git's true after the fact, and documents it in one paragraph of wt switch's after_long_help.

Three things follow:

  • A same-named branch keeps its tracking. --create release --base origin/release was getting unset too, though the tracking it lost was correct.
  • An explicit branch.autoSetupMerge is honoured. wt picks a different default; it no longer overrides the setting.
  • --create off a remote base no longer fails outright for anyone whose config left git no upstream to unset. Under branch.autoSetupMerge = false the command exited 128 — fatal: branch 'feature' has no upstream information — after creating the branch and the worktree.

The DWIM paths (wt switch release when only origin/release exists) are unaffected: they create feature from origin/feature, where simple and true agree. simple needs git ≥ 2.37; MINIMUM_GIT_VERSION is 2.43.

Testing

test_switch_create_from_remote_base_no_upstream becomes test_switch_create_from_remote_base_upstream, covering the same #713 property plus the three cases above. Full suite via cargo run -- hook pre-merge --yes.

Verification (git 2.55.0, debug wt, bare origin with main and release)

Behavior, before and after, in a clone with no local release:

$ wt switch --create feature --base origin/release --no-cd   # default config
$ git branch -vv
+ feature 9e98944 (…/src.feature) r2                          # no upstream — unchanged

$ git config branch.autoSetupMerge false                      # before: exit 1
$ wt switch --create feature --base origin/release --no-cd
✓ Created branch feature from origin/release and worktree @ …/src.feature
+ feature 9e98944 (…/src.feature) r2

$ git config branch.autoSetupMerge always                     # before: unset anyway
$ wt switch --create feature --base origin/release --no-cd
+ feature 9e98944 (…/src.feature) [origin/release] r2

$ wt switch release --no-cd                                   # DWIM, unchanged
+ release 9e98944 (…/src.release) [origin/release] r2

The failure this removes, on the pre-change binary:

$ git config branch.autoSetupMerge false
$ wt switch --create feature --base origin/release --no-cd
✗ git branch --unset-upstream -- feature failed (exit 128)
  fatal: branch 'feature' has no upstream information
$ git worktree list
/tmp/asm2/src         257f014 [main]
/tmp/asm2/src.feature 653f0dd [feature]                       # created, then the command failed

branch.autoSetupMerge semantics, straight from git, no wt involved:

$ git switch -c t-true origin/release                              # default `true`
* t-true aeb85e2 [origin/release] r2
$ git -c branch.autoSetupMerge=simple switch -c t-simple origin/release
* t-simple aeb85e2 r2
$ git -c branch.autoSetupMerge=simple switch -c release origin/release
* release aeb85e2 [origin/release] r2

And that git worktree add -b — the invocation wt actually runs — honours it the same way:

$ git -c branch.autoSetupMerge=simple worktree add -b wt-diff /tmp/asm/wt-diff origin/release
+ wt-diff aeb85e2 (/tmp/asm/wt-diff) r2
$ git -c branch.autoSetupMerge=simple worktree add -b foo /tmp/asm/foo origin/foo
+ foo aeb85e2 (/tmp/asm/foo) [origin/foo] r2

Refs #3912, #713 — automated triage

Creating from a remote base deliberately unsets the tracking git would
otherwise set to the base (#713), so a bare `git push` cannot push the
new work to the base branch. Nothing said so, leaving the branch with no
upstream and no stated way to publish it.

Refs #3912
@worktrunk-bot worktrunk-bot added the automated-fix Automated CI fix label Aug 25, 2026

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The behavior claim is right — switch.rs unsets the upstream whenever --create's base is a remote-tracking ref (the is_remote_tracking_branch(base) arm, citing #713) — and I reproduced the push.autoSetupRemote = true path exactly as written. One inline suggestion on the rationale sentence, which doesn't hold under git's default config.

One thing the paragraph under-describes: resolve_base_ref promotes a bare base to origin/<base> when the name exists on exactly one remote and not locally (the remotes.len() == 1 arm in src/commands/worktree/switch.rs), so --base release against a remote-only release lands in the same no-upstream state as the explicit --base origin/release the paragraph shows. Worth a few words if it fits, though the explicit form is the one #3912 hit.

Repro for the inline suggestion (git 2.55.0, default config)
$ git config --get push.default    # unset → "simple"
$ git worktree add -b feature ../ft origin/release
$ git branch -vv
* feature 48b8215 [origin/release] r1
$ git push
fatal: The upstream branch of your current branch does not match
the name of your current branch.  To push to the upstream branch
on the remote, use

    git push origin HEAD:release

Under push.default = upstream it does what the paragraph says:

$ git -c push.default=upstream push --dry-run
To ../origin.git
   48b8215..5c1fec7  feature -> release

So the unset protects the upstream configuration specifically; under simple both states refuse the bare push, just with different messages.

Comment thread src/cli/mod.rs Outdated
Also covers the bare `--base release` that resolve_base_ref promotes to
`origin/release`, and corrects the same overstatement in the code comment
above unset_upstream().
@max-sixty

Copy link
Copy Markdown
Owner

I'm confused though: if we switch to an upstream branch, don't we make a local branch and then that local branch has an upstream? or I'm thinking about it wrongly?

The paragraph sat next to the sentence about switching to a remote branch
creating a local *tracking* branch, without saying the two cases differ.
Name --create as the exception and say what the inherited tracking would
point at.
@worktrunk-bot

Copy link
Copy Markdown
Collaborator Author

You're thinking about it right — that's a different case from the one the paragraph covers, and the paragraph didn't say so.

wt switch release when only origin/release exists does exactly what you describe: local branch, upstream intact. The unset is gated on *create_branch (switch.rs#L1037-L1043), so it never fires on that path.

The difference is what the inherited tracking would point at. Switching to release gives a branch named release tracking origin/release — the names line up, so tracking is correct. --create feature --base origin/release gives a branch named feature tracking origin/release, i.e. pointing at someone else's branch, which is what #713 unset.

Pushed c51e0bb rewording the paragraph to name --create as the exception and say what the tracking would point at, rather than leaving it adjacent to the tracking-branch sentence with nothing marking the contrast.

Verified against a scratch repo (debug wt, git 2.55.0)

origin has main and release; the clone has no local release.

$ wt switch release --no-cd
✓ Created branch release (tracking origin/release) and worktree @ /tmp/scratch/src.release
$ git branch -vv
* main    54a1160 [origin/main] r1
+ release f7679ef (/tmp/scratch/src.release) [origin/release] r2
$ wt switch --create feature --base origin/release --no-cd
✓ Created branch feature from origin/release and worktree @ /tmp/scratch/src.feature
$ git branch -vv
+ feature f7679ef (/tmp/scratch/src.feature) r2            # no upstream
* main    54a1160 [origin/main] r1
+ release f7679ef (/tmp/scratch/src.release) [origin/release] r2

One corner: --create release --base origin/release — same name, so tracking would have been fine — also gets unset, since the condition doesn't compare names. wt already routes that one out with a hint (switch.rs#L616):

$ wt switch --create release --base origin/release --no-cd
↳ To switch to the remote branch, delete this branch and run without --create: wt remove --foreground release && wt switch release
✓ Created branch release from origin/release and worktree @ /tmp/scratch/src2.release

@max-sixty

Copy link
Copy Markdown
Owner

ok, does this act differently to git? do we need to document it?!

@worktrunk-bot

Copy link
Copy Markdown
Collaborator Author

Yes — this is the one place wt switch deviates from git, and that's the argument for documenting it.

Git sets the upstream on every form that creates a branch from a remote-tracking ref — git worktree add -b, git switch -c, git checkout -b, git branch — because branch.autoSetupMerge defaults to true. wt switch --create --base origin/release unsets it afterwards (switch.rs#L1031-L1043, #713). The no---create path you asked about last time matches git exactly — wt switch release is git's DWIM, upstream intact. So the deviation is scoped to --create off a remote base, which is exactly the sentence the paragraph adds.

The caveat is that the deviation only buys something under push.default = upstream, which isn't git's default. Under simple, git already refuses the bare push from a feature branch tracking origin/release, so on a default config the unset trades one refusal (no upstream configured) for another (upstream branch does not match). If you'd rather drop the deviation than document it, that's #713 to reopen — and this paragraph goes away with it. Documenting is the smaller move given #3912 is the third thread to land here.

One thing docs can't do: the surprise arrives at git push, in a worktree, minutes after the wt switch that caused it. The creation line (✓ Created branch feature from origin/release and worktree @ …) is silent about tracking, while the auto-created-from-remote path does say (tracking origin/release). Saying it there too would reach people who never open --help — separate change, happy to leave it.

Repro (git 2.55.0, default config, no wt involved)

origin has main and release; the clone has no local release.

$ git config --get branch.autoSetupMerge      # unset → true
$ git worktree add -b wtb ../wtb origin/release
$ git switch -c swb origin/release
$ git checkout -b cob origin/release
$ git branch brb origin/release
branch 'brb' set up to track 'origin/release'.

$ git branch -vv
  brb    4bc6718 [origin/release] r2
  cob    4bc6718 [origin/release] r2
* main   478fe79 [origin/main] r1
  swb    4bc6718 [origin/release] r2
+ wtb    4bc6718 (/tmp/gitcheck/wtb) [origin/release] r2

All four track origin/release. wt switch --create feature --base origin/release leaves feature with no upstream (repro in the comment above).

@max-sixty

Copy link
Copy Markdown
Owner

so should we just copy git's behavior?

…tream

`wt switch --create <name> --base origin/<branch>` ran `git branch
--unset-upstream` afterwards so the new branch could not push to the base
(#713). Git ships that exact rule as `branch.autoSetupMerge = simple`:
tracking is set only when the start point is a remote-tracking branch and
the new branch has the same name as the remote branch. Default to it for
the `--create` paths rather than undoing git's `true` after the fact.

Three things follow. A branch that shares its base's name — `--create
release --base origin/release` — keeps the tracking, which was always
correct and was being unset too. An explicit `branch.autoSetupMerge` is
honoured, so `wt` picks a different default rather than overriding the
setting. And `wt switch --create` no longer fails outright for anyone
whose config left git no upstream to unset: under `branch.autoSetupMerge
= false` the command exited 128 with "fatal: branch 'feature' has no
upstream information", after the branch and worktree had been created.

The DWIM paths are unaffected: they create `feature` from
`origin/feature`, where `simple` and `true` agree.
@worktrunk-bot worktrunk-bot changed the title docs(switch): say why a branch off a remote base has no upstream fix(switch): track a remote base only when the names match Aug 26, 2026
@worktrunk-bot

Copy link
Copy Markdown
Collaborator Author

Not git's default — that's the config #713 reported an accidental push to master under. But git ships a second mode that is exactly the rule the unset was approximating, so "copy git" is available without giving that up: branch.autoSetupMerge = simple sets tracking only when the start point is a remote-tracking branch and the new branch has the same name as the remote branch. Git's default true drops that second clause; simple has been available since 2.37 and MINIMUM_GIT_VERSION is 2.43.

Pushed 61c66de switching to it — git -c branch.autoSetupMerge=simple worktree add -b … on the --create paths, and the post-hoc git branch --unset-upstream deleted (switch.rs#L967-L981). Three things change, and the third is the one that decided it for me:

  • --create release --base origin/release keeps its tracking. The old condition didn't compare names, so it unset the one case where inherited tracking was right — the corner I flagged two comments up.
  • An explicit branch.autoSetupMerge now wins. wt picks a different default; it no longer overrides always or inherit.
  • wt switch --create --base origin/X was failing outright for anyone whose config left git no upstream to unset. With branch.autoSetupMerge = false it exited 128 with fatal: branch 'feature' has no upstream information — after creating the branch and the worktree. So the status quo was wrong however this question goes; both candidate answers fix it.

The docs paragraph stays but shrinks to naming the config, since the behavior is now a git setting rather than a wt invention. If you'd rather have plain true after all, it's that one commit to revert and the paragraph goes with it.

Verified against scratch repos (git 2.55.0, debug wt)

origin has main and release; the clone has no local release.

The failure on the pre-change binary:

$ git config branch.autoSetupMerge false
$ wt switch --create feature --base origin/release --no-cd
✗ git branch --unset-upstream -- feature failed (exit 128)
  fatal: branch 'feature' has no upstream information
$ git worktree list
/tmp/asm2/src         257f014 [main]
/tmp/asm2/src.feature 653f0dd [feature]

After, across configs:

$ wt switch --create feature --base origin/release --no-cd    # default config
+ feature 9e98944 (…/src.feature) r2                           # no upstream, as before

$ git config branch.autoSetupMerge false
$ wt switch --create feature --base origin/release --no-cd
✓ Created branch feature from origin/release and worktree @ …/src.feature

$ git config branch.autoSetupMerge always
$ wt switch --create feature --base origin/release --no-cd
+ feature 9e98944 (…/src.feature) [origin/release] r2

$ wt switch release --no-cd                                    # DWIM path, unchanged
+ release 9e98944 (…/src.release) [origin/release] r2

simple vs true in git itself, and on the worktree add -b form wt actually runs:

$ git switch -c t-true origin/release                              # default `true`
* t-true aeb85e2 [origin/release] r2
$ git -c branch.autoSetupMerge=simple switch -c t-simple origin/release
* t-simple aeb85e2 r2
$ git -c branch.autoSetupMerge=simple switch -c release origin/release
* release aeb85e2 [origin/release] r2

$ git -c branch.autoSetupMerge=simple worktree add -b wt-diff /tmp/asm/wt-diff origin/release
+ wt-diff aeb85e2 (/tmp/asm/wt-diff) r2
$ git -c branch.autoSetupMerge=simple worktree add -b foo /tmp/asm/foo origin/foo
+ foo aeb85e2 (/tmp/asm/foo) [origin/foo] r2

test_switch_create_from_remote_base_no_upstream becomes test_switch_create_from_remote_base_upstream and covers all four rows. Full suite via cargo run -- hook pre-merge --yes: two failures, both sandbox-environmental and unrelated — test_copy_ignored_preserves_file_executable_permissions (runner umask is 0002, so 0664 not 0644) and test_powershell_skipped_when_installed_no_profile.

@mjakl

mjakl commented Aug 27, 2026

Copy link
Copy Markdown

Thanks—this also addresses #3937. I encountered the failure with branch.autoSetupMerge=simple: Git creates the differently named branch without an upstream, then wt switch --create fails while trying to unset an upstream that does not exist. I filed #3937 before finding this PR.

@worktrunk-bot

Copy link
Copy Markdown
Collaborator Author

Thanks for the repro in #3937 — it ran unmodified against builds of both main and this branch, and yes, this PR fixes it. Full before/after matrix is on the issue; two parts worth carrying over here.

On main the failure isn't specific to simple: false and inherit fail identically, because the unset_upstream() call this PR removes was unconditional. Under this branch all three exit 0 with no upstream.

The flip side is that an explicit branch.autoSetupMerge = true (or always) now leaves the new branch tracking origin/main, where main unset it. That's the "your config wins" half of the change rather than a gap — wt only supplies simple as a default when you haven't set the key — but it is a real behavior change for anyone who set true deliberately.

@max-sixty
max-sixty merged commit 4e9f706 into main Aug 28, 2026
15 of 18 checks passed
@max-sixty
max-sixty deleted the docs/issue-3912-remote-base-upstream branch August 28, 2026 15:30
max-sixty added a commit that referenced this pull request Aug 29, 2026
…nfig says (#3950)

## Problem

`wt switch --create <name> --base origin/<branch>` must not leave the
new branch tracking a differently-named base: under `push.default =
upstream` a bare `git push` then pushes the new work onto the base
branch ([#713](#713)).

Since #3913 that came from `-c branch.autoSetupMerge=simple`, injected
only when the user had not set the key. So the outcome still depended on
the user's git config, and two of git's five values reach a wrong
answer: `true` and `always` track a differently-named base (the #713
footgun is still live), while `false` and `inherit` deny a *same*-named
branch the tracking that is the point of it — including the DWIM `wt
switch feature` from `origin/feature`, which the docs promise is a
tracking branch.

## Solution

Force the `-c` instead of defaulting it, on every `git worktree add` `wt
switch` runs. `-c` outranks every config file, so one rule now decides
the upstream whatever the user has configured: a new branch tracks the
remote branch it starts from only when the two share a name. DWIM always
shares it, so it always tracks; `--create` from a differently-named base
gets no upstream.

Tests: `test_switch_create_from_remote_base_upstream` becomes a matrix
over all six `branch.autoSetupMerge` values (unset, simple, false,
inherit, true, always) × three base spellings (`origin/release`, the
bare `release` that resolves to it, and `refs/remotes/origin/staging`);
`test_switch_dwim_from_remote_tracks` pins the DWIM half over the values
that used to decline.

## Why not `--track` / `--no-track`

The obvious alternative is for `wt` to pick git's explicit flags from a
name comparison of its own. I built that first; it passed the full
suite, and it is wrong twice over.

<details>
<summary>Three reproduced defects in the explicit-flags
version</summary>

**The predicate is wrong.** `strip_remote_prefix` splits
`<remote>/<branch>` at the first slash, but git maps a remote-tracking
ref back to its branch through the *fetch refspec*. With a remote named
`team/fork`, the two disagree and the verdict inverts both ways:

```console
$ git remote add team/fork <url> && git fetch team/fork
$ wt switch --create fork/release --base team/fork/release
$ git config branch.fork/release.merge
refs/heads/release
```

A bare `git push` under `push.default = upstream` then lands on
`release` — #713, reintroduced. The same base with `--create release`
got *no* upstream, the opposite error. A refspec renaming into a
sub-namespace (`+refs/heads/*:refs/remotes/origin/mirror/*`) does the
same with no unusual remote name.

**`--track` is a hard demand where `simple` is best-effort.** In a
single-branch clone holding a hand-fetched ref, it fails the whole
command — after the branch name has already been taken:

```console
$ git clone --single-branch -b main <url> && git fetch origin release:refs/remotes/origin/release
$ wt switch --create release --base origin/release
fatal: cannot set up tracking information; starting point 'origin/release' is not a branch
```

That is the matching-name case, i.e. exactly what the feature exists
for, and it is the same "fails outright" class #3913 had just removed.

**Qualified spellings lose tracking.** `--base
refs/remotes/origin/release` and `--base remotes/origin/release` name
the same ref as `origin/release` but don't match `short_name`, so they
got `--no-track`.

</details>

All three come from `wt` computing the name match itself, which it
cannot do correctly — only git knows the refspec mapping. So `wt`
decides the *rule* and git applies it. The first two are now regression
tests (`test_switch_create_base_on_remote_with_slash`,
`test_switch_create_base_outside_fetch_refspec`), so a future
simplification to `--track` fails the suite rather than shipping.

## Verification

Measured end-to-end on scratch clones across all six
`branch.autoSetupMerge` values: a differing name gets no upstream and a
matching name tracks, identically in every one.

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
social4hyq pushed a commit to social4hyq/homebrew-core that referenced this pull request Sep 20, 2026
worktrunk 0.76.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

- **`wt switch --execute` takes a program, not a shell string**: `-x` names one program, with everything after `--` passed as literal argv. Worktrunk spawns it as a child rather than running it in your interactive shell, so shell functions and its `cd` no longer reach you. `-x sh -- -c '…'` recovers shell syntax, not functions. (Breaking: existing `-x` strings, plus `WORKTRUNK_DIRECTIVE_EXEC_FILE` and `WORKTRUNK_SHELL`.) ([#3977](max-sixty/worktrunk#3977), closes [#2860](max-sixty/worktrunk#2860), fixes [#3944](max-sixty/worktrunk#3944), thanks @omgreenfield for testing the migration path)

- **Worktrunk decides tracking for the branches it creates**: a new branch gets an upstream only when its name matches the remote branch it starts from, whatever `branch.autoSetupMerge` says: `--create release --base origin/release` tracks, `--create feature --base origin/release` does not. Under `autoSetupMerge = false` this previously exited 128 after creating the worktree. ([#3913](max-sixty/worktrunk#3913), [#3950](max-sixty/worktrunk#3950), fixes [#3937](max-sixty/worktrunk#3937), thanks @mjakl for reporting and diagnosing)

- **Retired config keys are no longer read**: `commit.generation.template-file` and `squash-template-file` are gone — put the file's contents in `template` or `squash-template`. There is no migration: the key stays, warns as unknown on every load, and the prompt reverts to the built-in default. Setting both `template` and `template-file` used to fail the load; it now loads `template`. `switch.picker.timeout-ms` likewise warns rather than being stripped. (Breaking.) ([#3949](max-sixty/worktrunk#3949))

- **Project aliases and hooks can use `wt switch --execute`**: their bodies previously refused it. Worktrunk now starts the program itself instead of handing shell text to your shell, and the command-approval gate remains the control on project-defined commands. ([#3977](max-sixty/worktrunk#3977))

- **Established automation and LLM customization interfaces are now stable**: `wt step eval`, `wt step for-each`, `wt step prune`, LLM branch summaries, `wt config state vars`, and `commit.generation.template-append` are no longer marked experimental. ([#3949](max-sixty/worktrunk#3949))

- **`wt step prune` stages removals concurrently again**: only the `git worktree remove` teardowns serialize, so dirty checks, fsmonitor shutdown, and the trash rename overlap. The `prune_e2e/live` benchmark median went from 377 ms to 222 ms. ([#3954](max-sixty/worktrunk#3954))

- **`wt config approvals add` reads as a review rather than a warning**: it opens with a cyan `Review 1 command for repo:` instead of a yellow `▲ … needs approval to execute`. The execution-time gate is unchanged. ([#3953](max-sixty/worktrunk#3953))

### Fixed

- **The `wt switch` picker survives a preview longer than 65,535 lines**: skim keeps the pane's line count in a `u16`, so a larger diff aborted `wt` with exit 101 seconds after it painted — on screen the picker looked like it closed by itself. Panes now cap at 60,000 lines and say so. ([#3959](max-sixty/worktrunk#3959), fixes [#3958](max-sixty/worktrunk#3958), thanks @sandertammesoo for reporting and diagnosing)

- **The nushell wrapper propagates exit codes without a POSIX shell**: it reported a failing `wt` by spawning `sh`, so on Windows every failing command printed a ``Command `sh` not found`` trace and exit 1 whatever the real code was. Nushell's wrapper is a static file, so rerun `wt config shell install` to pick this up. ([#3945](max-sixty/worktrunk#3945))

- **The picker no longer shows one detached worktree's diff for another**: previews were keyed by branch name, so every detached row shared the key `(detached)`. Rows now key by canonical identity, which also lets `wt remove feature ~/repo.feature` plan that worktree once instead of failing on the second. ([#3926](max-sixty/worktrunk#3926))

- **`wt switch pr:<n>` gives `pre-switch` hooks the real branch**: `branch` and `target` held the literal `pr:3933`, and `target_worktree_path` was unset even when that branch had a worktree. `pr_number` and `pr_url` are now set for same-repo PRs, not only forks. ([#3941](max-sixty/worktrunk#3941), fixes [#3934](max-sixty/worktrunk#3934), thanks @robsonpeixoto for reporting)

- **`wt config shell install` no longer replaces an rc file created while it was checking**: the missing-file path refuses to clobber and fails with a rerun hint. A dangling rc symlink is preserved and rejected rather than overwritten. ([#3929](max-sixty/worktrunk#3929))

- **`wt switch --create X --base pr:<n>` works when the PR branch has no local branch**: the fetch writes only the remote-tracking ref, and git won't expand a bare name to one, so the command failed with `No branch, tag, or commit named …`. The base is now named under its remote. ([#3951](max-sixty/worktrunk#3951))

- **`wt switch --create` streams `git worktree add` instead of stalling**: where a sandbox denied the timed wait's pipe or `sigaction`, the command waited out git's entire run and then reported `Failed to wait for command` for work that had already succeeded. ([#3881](max-sixty/worktrunk#3881))

- **Claude Code activity markers stay on the session's launch worktree**: the plugin's marker hooks pass `-C "$CLAUDE_PROJECT_DIR"`, so a `cd` mid-session no longer marks another repository. A session launched outside a repository gets no marker. ([#3956](max-sixty/worktrunk#3956))

### Documentation

- **`--execute` documents how to get a variable into a shell body**: each position is substituted verbatim, so a variable spliced into `sh -c` text is re-parsed by that shell and splits on spaces. The help now shows passing it as a separate argument and referencing it positionally. ([#3977](max-sixty/worktrunk#3977))

- **The clone-local default-branch override is documented**: `wt config state default-branch set` writes `worktrunk.default-branch` to the clone's local git config — machine-local, never committed, and shared by every linked worktree. `set` warns when the branch isn't checked out locally, and `wt list` skips its comparisons until it is. ([#3947](max-sixty/worktrunk#3947), [#3948](max-sixty/worktrunk#3948), fixes [#3946](max-sixty/worktrunk#3946), thanks @danielo515 for the request)

- **The docs site renders code with the CLI's own colors**: terminal examples reproduce each snapshot's exact ANSI roles, while command references and the homepage command comparison get clap and shell syntax color from paired light/dark themes. File excerpts gain a path tab, and code wraps instead of scrolling on mobile. ([#3936](max-sixty/worktrunk#3936), [#3938](max-sixty/worktrunk#3938), [#3939](max-sixty/worktrunk#3939), [#3942](max-sixty/worktrunk#3942))

- **The `pre-start` upstream example uses `--base`**: it showed `wt switch --create feature origin/feature`, which `wt switch` rejects. ([#3955](max-sixty/worktrunk#3955))

### Internal

- **Library API rework** (Breaking library API): `cargo-semver-checks` fails ten lints — `RemoteRefProvider` and its provider structs give way to `ForgeKind` dispatch, the `--execute` rework retires the fish and PowerShell `ShellEscapeMode` escapes and `WORKTRUNK_SHELL_ENV_VAR`, `BranchRef` swaps `full_ref`/`worktree_path` for `id`, and accessors including `Branch::unset_upstream` and `ProjectConfig::ci_platform` are gone. ([#3913](max-sixty/worktrunk#3913), [#3926](max-sixty/worktrunk#3926), [#3949](max-sixty/worktrunk#3949), [#3963](max-sixty/worktrunk#3963), [#3977](max-sixty/worktrunk#3977))

- **Hook pipelines and `wt list` collection lost two indirection layers**: the background pipeline carries `PreparedStep` directly, and 15 marker task types collapse into one exhaustive dispatch. The synthesized-`--force` submodule path keeps its dirty re-check and destructive command under one lock, so prune's narrower locking doesn't widen that window. ([#3964](max-sixty/worktrunk#3964), [#3954](max-sixty/worktrunk#3954))

## Install worktrunk 0.76.0

### Install prebuilt binaries via shell script

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

|  File  | Platform | Checksum |
|--------|----------|----------|
| [worktrunk-aarch64-apple-darwin.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.76.0/worktrunk-aarch64-apple-darwin.tar.xz) | Apple Silicon macOS | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.76.0/worktrunk-aarch64-apple-darwin.tar.xz.sha256) |
| [worktrunk-x86_64-ap

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

Labels

automated-fix Automated CI fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants