Skip to content

wt switch --create exits 1 after creating the worktree with branch.autoSetupMerge=simple #3937

Description

@mjakl

Summary

With branch.autoSetupMerge=simple, creating a branch whose name differs from its remote base leaves the new branch without an upstream, as documented by Git.

Worktrunk then unconditionally runs git branch --unset-upstream for a remote-tracking base. Git reports that there is no upstream to unset, and Worktrunk exits 1.

By that point, the branch and worktree have already been created successfully. With --format=json, stdout is empty, so automation receives a failure and no result even though the requested worktree exists.

Environment

$ uname -srmo
Linux 7.1.9-arch1-2 x86_64 GNU/Linux
# exit 0

$ wt --version
wt v0.75.0
# exit 0

$ git --version
git version 2.55.0
# exit 0

$ git config --show-origin --get branch.autoSetupMerge
file:.git/config	simple
# exit 0

The reproduction below isolates both Git and Worktrunk from user configuration. It uses only repository-local Git configuration and a local bare remote.

Minimal deterministic reproduction

#!/usr/bin/env bash
set -euo pipefail

tmp=$(mktemp -d /tmp/wt-v075-repro.XXXXXX)
trap 'rm -rf "$tmp"' EXIT

# Do not read the user's Git or Worktrunk configuration.
export HOME="$tmp/home"
export XDG_CONFIG_HOME="$tmp/xdg"
export GIT_CONFIG_GLOBAL=/dev/null
export GIT_CONFIG_SYSTEM=/dev/null
mkdir -p "$HOME" "$XDG_CONFIG_HOME"

# Create a local bare remote with one commit on main.
git init --bare --quiet --initial-branch=main "$tmp/origin.git"
git clone --quiet "$tmp/origin.git" "$tmp/seed"
printf 'initial\n' >"$tmp/seed/README.md"
git -C "$tmp/seed" add README.md
git -C "$tmp/seed" \
  -c user.name='Repro User' \
  -c user.email=repro@example.invalid \
  commit --quiet -m initial
git -C "$tmp/seed" push --quiet origin main

# Fresh clone containing only repository-local autoSetupMerge configuration.
git clone --quiet "$tmp/origin.git" "$tmp/repo"
git -C "$tmp/repo" config --local branch.autoSetupMerge simple
cd "$tmp/repo"

git config --show-origin --get branch.autoSetupMerge

set +e
wt switch --create oscar/recover-worktrunk \
  --base origin/main \
  --no-cd \
  --format=json \
  >"$tmp/stdout" 2>"$tmp/stderr"
status=$?
set -e

printf 'exit=%s\n' "$status"
printf 'stdout_bytes=%s\n' "$(wc -c <"$tmp/stdout")"
printf '%s\n' '--- stdout ---'
cat "$tmp/stdout"
printf '%s\n' '--- stderr ---'
cat "$tmp/stderr"

printf '%s\n' '--- resulting worktrees ---'
git worktree list --porcelain

printf '%s\n' '--- resulting upstream ---'
git for-each-ref \
  '--format=%(refname:short) upstream=%(upstream:short)' \
  refs/heads/oscar/recover-worktrunk

Actual result

Captured output from the failing command:

exit=1
stdout_bytes=0
--- stdout ---
--- stderr ---
✗ git branch --unset-upstream -- oscar/recover-worktrunk failed (exit 128)
  fatal: branch 'oscar/recover-worktrunk' has no upstream information

Despite that failure, the same run showed that the branch and linked worktree exist:

--- resulting worktrees ---
worktree /tmp/wt-v075-capture.agRkJW/repo
HEAD d8338b7e8fdefe481a2f871e3c7327ed215ba0aa
branch refs/heads/main

worktree /tmp/wt-v075-capture.agRkJW/repo.oscar-recover-worktrunk
HEAD d8338b7e8fdefe481a2f871e3c7327ed215ba0aa
branch refs/heads/oscar/recover-worktrunk

--- resulting upstream ---
oscar/recover-worktrunk upstream=

Running the failing Git operation directly confirms its exit status:

$ git branch --unset-upstream oscar/recover-worktrunk
fatal: branch 'oscar/recover-worktrunk' has no upstream information
# exit 128

The temporary repository shown above was removed after verification.

Expected result

The command should:

  1. create the branch and worktree;
  2. leave the new branch without an upstream, preserving the safety behavior from wt switch --create --base=origin/master sets upstream to master (dangerous) #713;
  3. emit the normal {"action":"created", ...} JSON result; and
  4. exit 0.

An absent upstream already satisfies the safety requirement. It should therefore be treated as success rather than as an error from the overall operation.

Cause and effect

Git documents branch.autoSetupMerge=simple as setting up tracking only when:

  • the start point is a remote-tracking branch; and
  • the new branch has the same name as the remote branch.

See git-branch documentation.

Here the names differ:

new branch:  oscar/recover-worktrunk
remote base: origin/main

The resulting sequence is:

  1. Worktrunk creates oscar/recover-worktrunk from origin/main.
  2. Because branch.autoSetupMerge=simple applies, Git does not configure an upstream.
  3. The branch and worktree now exist and are in the desired safe state.
  4. Worktrunk recognizes origin/main as a remote-tracking base and calls unset_upstream() unconditionally.
  5. git branch --unset-upstream exits 128 because there is nothing to unset.
  6. Worktrunk propagates that as exit 1 before emitting the JSON result.

The relevant v0.75.0 code is:

This safety path originated in #713 and PR #717.

At the time of this report, source inspection of main at e5f9589c showed the same unconditional call.

Control

A fresh clone with branch.autoSetupMerge=true does not fail:

$ git config --local branch.autoSetupMerge true
# exit 0

$ wt switch --create oscar/recover-worktrunk --base origin/main --no-cd --format=json
{"action":"created","branch":"oscar/recover-worktrunk","path":"/tmp/worktrunk-v075-repro.mD6nA3/control-true.oscar-recover-worktrunk","created_branch":true,"base_branch":"origin/main"}
✓ Created branch oscar/recover-worktrunk from origin/main and worktree @ /tmp/worktrunk-v075-repro.mD6nA3/control-true.oscar-recover-worktrunk
↳ To customize worktree locations, run wt config create
# exit 0

$ git for-each-ref \
    '--format=%(refname:short) upstream=%(upstream:short)' \
    refs/heads/oscar/recover-worktrunk
oscar/recover-worktrunk upstream=
# exit 0

With true, Git creates the tracking relationship for a remote-tracking start point, so Worktrunk has an upstream to remove. The final safe state is otherwise the same.

Verified workarounds

Recover after the failure

The failed invocation has already created the worktree. Switch to the existing branch without --create:

$ wt switch oscar/recover-worktrunk --no-cd --format=json
{"action":"existing","branch":"oscar/recover-worktrunk","path":"/tmp/worktrunk-v075-repro.mD6nA3/failure.oscar-recover-worktrunk"}
○ Switched to worktree for oscar/recover-worktrunk @ /tmp/worktrunk-v075-repro.mD6nA3/failure.oscar-recover-worktrunk
# exit 0

Resolve the remote-tracking base to its commit first

This preserves the current origin/main commit while avoiding the remote-base cleanup path:

$ base=$(git rev-parse origin/main)

$ wt switch --create oscar/recover-sha \
    --base "$base" \
    --no-cd \
    --format=json
{"action":"created","branch":"oscar/recover-sha","path":"/tmp/worktrunk-v075-repro.mD6nA3/workaround-sha.oscar-recover-sha","created_branch":true,"base_branch":"1e80abd0b884ad69ed02fea298772235669be5bb"}
✓ Created branch oscar/recover-sha from 1e80abd0b884ad69ed02fea298772235669be5bb and worktree @ /tmp/worktrunk-v075-repro.mD6nA3/workaround-sha.oscar-recover-sha
↳ To customize worktree locations, run wt config create
# exit 0

$ git for-each-ref \
    '--format=%(refname:short) upstream=%(upstream:short)' \
    refs/heads/oscar/recover-sha
oscar/recover-sha upstream=
# exit 0

The created branch SHA was also verified to equal the resolved origin/main SHA.

Not verified

  • I did not run the reproduction on operating systems or Git versions other than Linux x86_64 with Git 2.55.0.
  • I did not build and run the current main branch; the statement about main above is source inspection only.
  • I did not bisect Worktrunk releases to identify the first released version containing this failure.
  • Other branch.autoSetupMerge values that can omit tracking, such as false, were not tested.

This report was generated by an AI coding agent. The reproduction and outputs were verified locally before posting.

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