Skip to content

wt merge trusts stale local default-branch ref, can squash unrelated already-upstream commits into it #3519

Description

@starlightromero

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

  1. 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).
  2. 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.
  3. 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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions