Bug Report
Description
wt merge, run from a feature-branch worktree that is itself correctly up to date with origin/<default-branch>, can still corrupt the primary checkout's local default-branch ref if that primary checkout has fallen behind origin/<default-branch>. Rather than fetching origin and using origin/<default-branch> as the merge/rebase target, wt merge appears to diff/squash against the primary checkout's local (stale) branch ref and then fast-forward that local ref via what looks like a local, non-network git push <path> HEAD:<default-branch>-style mechanism — bypassing any comparison against origin/<default-branch> first.
Repro
- Primary checkout's local
main is N commits behind origin/main (e.g. left checked out on an unrelated feature branch for a while, never fetched/fast-forwarded).
- From a separate worktree, correctly created via
wt switch --create <branch> --base origin/main (confirmed zero divergence from origin/main at creation time), add one commit, push it, open a PR containing only that one commit.
- Run
wt merge from within that worktree.
Observed
wt merge reported "Squashing 13 commits into a single commit" and produced a squash commit combining the 1 real commit with 12 already-merged-to-origin/main commits (visible by PR title in the generated squash-commit message) into the local default-branch ref of the primary checkout — not the worktree's own branch. git reflog on the affected branch showed a push action at the corrupted commit, consistent with a local ref update rather than a real network operation.
Impact
In our case no network push occurred (origin/main was untouched throughout — verified via git fetch origin + git log origin/main before and after; the real PR remained open/unmerged on GitHub the whole time), so it was recoverable via git branch -f main origin/main. But had that local main subsequently been pushed by any later action, it would have introduced a squash commit duplicating content already on origin/main under new commit SHAs.
Relationship to #3471
Same underlying class as #3471 (wt's cached default-branch state not refreshing from origin/HEAD) — trusting a local ref instead of origin/<default-branch> — but a different subcommand (merge rather than switch --create) and a more dangerous consequence: it mutates/corrupts an existing local branch ref (potentially the primary checkout's) rather than just creating a new branch that's missing some upstream content.
Suggested fix
wt merge should fetch origin and use origin/<default-branch> as its merge/rebase target (or at minimum warn/fail) whenever the local default-branch ref is behind origin/<default-branch>, rather than trusting the local ref uncritically — mirroring whatever fix addresses #3471 for wt switch --create.
Workaround
Before running wt merge, ensure the primary checkout's local default branch is current: git fetch origin && git log origin/<default-branch>..<default-branch> (check for unpushed-only local commits first) && git branch -f <default-branch> origin/<default-branch> if safe. Or bypass wt merge entirely and merge the PR directly via gh pr merge <PR> --squash --delete-branch.
Environment
- worktrunk version: 0.68.0 (Homebrew)
Bug Report
Description
wt merge, run from a feature-branch worktree that is itself correctly up to date withorigin/<default-branch>, can still corrupt the primary checkout's local default-branch ref if that primary checkout has fallen behindorigin/<default-branch>. Rather than fetchingoriginand usingorigin/<default-branch>as the merge/rebase target,wt mergeappears to diff/squash against the primary checkout's local (stale) branch ref and then fast-forward that local ref via what looks like a local, non-networkgit push <path> HEAD:<default-branch>-style mechanism — bypassing any comparison againstorigin/<default-branch>first.Repro
mainis N commits behindorigin/main(e.g. left checked out on an unrelated feature branch for a while, never fetched/fast-forwarded).wt switch --create <branch> --base origin/main(confirmed zero divergence fromorigin/mainat creation time), add one commit, push it, open a PR containing only that one commit.wt mergefrom within that worktree.Observed
wt mergereported "Squashing 13 commits into a single commit" and produced a squash commit combining the 1 real commit with 12 already-merged-to-origin/maincommits (visible by PR title in the generated squash-commit message) into the local default-branch ref of the primary checkout — not the worktree's own branch.git reflogon the affected branch showed apushaction at the corrupted commit, consistent with a local ref update rather than a real network operation.Impact
In our case no network push occurred (
origin/mainwas untouched throughout — verified viagit fetch origin+git log origin/mainbefore and after; the real PR remained open/unmerged on GitHub the whole time), so it was recoverable viagit branch -f main origin/main. But had that localmainsubsequently been pushed by any later action, it would have introduced a squash commit duplicating content already onorigin/mainunder new commit SHAs.Relationship to #3471
Same underlying class as #3471 (
wt's cached default-branch state not refreshing fromorigin/HEAD) — trusting a local ref instead oforigin/<default-branch>— but a different subcommand (mergerather thanswitch --create) and a more dangerous consequence: it mutates/corrupts an existing local branch ref (potentially the primary checkout's) rather than just creating a new branch that's missing some upstream content.Suggested fix
wt mergeshould fetchoriginand useorigin/<default-branch>as its merge/rebase target (or at minimum warn/fail) whenever the local default-branch ref is behindorigin/<default-branch>, rather than trusting the local ref uncritically — mirroring whatever fix addresses #3471 forwt switch --create.Workaround
Before running
wt merge, ensure the primary checkout's local default branch is current:git fetch origin && git log origin/<default-branch>..<default-branch>(check for unpushed-only local commits first)&& git branch -f <default-branch> origin/<default-branch>if safe. Or bypasswt mergeentirely and merge the PR directly viagh pr merge <PR> --squash --delete-branch.Environment