Skip to content

fix(git): resolve worktree base refs through upstream and local fallbacks - #21

Open
JLarky wants to merge 1 commit into
mainfrom
fix/resolve-remote-tracking-base-fork
Open

JLarky wants to merge 1 commit into
mainfrom
fix/resolve-remote-tracking-base-fork

Conversation

@JLarky

@JLarky JLarky commented Jul 31, 2026 •

Copy link
Copy Markdown
Owner

Attempting to run a command using github.com/JLarky/lima-escape

What Changed

resolveRemoteTrackingCommit (apps/server/src/vcs/GitVcsDriverCore.ts) now resolves in order instead of assuming <fallbackRemote>/<refName> exists:

  1. an already remote-qualified ref (origin/foo) — unchanged
  2. new: the branch's configured upstream (<branch>@{upstream})
  3. <fallbackRemote>/<refName> — unchanged
  4. new: the local branch, returning remoteRefName: null
  5. otherwise a message naming the ref that was looked for, instead of git's stderr

GitResolveRemoteTrackingCommitResult.remoteRefName widens to string | null so callers can distinguish a remote base from a local one.

Why

Creating a worktree with "start from origin" failed for any local branch whose name does not exist verbatim on the remote:

Git command failed in GitVcsDriver.resolveRemoteTrackingCommit (/home/…/t3code): fatal: Needed a single revision

The old code built the ref by string concatenation and never consulted the branch's upstream, so even a branch with a correctly configured upstream failed — which makes the error look arbitrary. Reproduced against this repo: the default checkout sits on a local branch main-main that was never pushed, so the server ran

git rev-parse --verify refs/remotes/origin/main-main^{commit}   # fatal: Needed a single revision

62cf46175 upstream flipped newWorktreesStartFromOrigin to default true, so this path now runs for everyone rather than only for people who opted in.

Why the local fallback rather than origin/HEAD: if the selected branch is an unpushed feature branch with real commits, silently basing the worktree on origin/HEAD drops that work out of the base and the agent proceeds on the wrong tree with nothing in the UI saying so. refs/remotes/origin/HEAD is also frequently unset locally (it is unset in this clone), so it needs a guard regardless. Falling back to the local ref never changes which branch you asked for.

Precedent: GitManager.resolveBaseRangeRef already calls this same resolver with Effect.orElseSucceed(() => baseBranch) — falling back to the local ref is already this codebase's behavior for this resolver. The worktree bootstrap path was the inconsistent one.

Scope

Deliberately excluded, to keep this reviewable as an upstream candidate:

  • No origin/HEAD fallback (see above).
  • No change to the branch picker's default. Defaulting it to the remote default branch instead of whatever branch the repo is sitting on is a real improvement, but it is a separate change (baseBranchForWorktree, apps/web/src/components/ChatView.tsx).
  • No change to the baseRefName metadata passed to createWorktree. It is unrelated to this bug and behaviorally inert, since createWorktree already strips the remote prefix.

Testing

Four focused integration tests in apps/server/src/vcs/GitVcsDriverCore.test.ts, each against a real temp repo:

  • resolves a local branch through its configured upstream (the main-main case)
  • falls back to a local branch when no remote counterpart exists
  • prefers the branch's own upstream over the fallback remote — pins the cross-remote behavior
  • fails with an actionable error when neither a remote nor a local ref exists

vp test run src/vcs/GitVcsDriverCore.test.ts → 33 passed. vp fmt --check clean, vp lint clean, apps/server typecheck passes (only pre-existing findings in fork-only say-to-me/voiceNotesProxy.ts).

🤖 Generated with Claude Code

…acks

`resolveRemoteTrackingCommit` built its remote-tracking ref by concatenating
`<fallbackRemote>/<refName>`, never consulted the branch's configured upstream,
and had no fallback. Creating a worktree with "start from origin" therefore
failed with a raw `fatal: Needed a single revision` for any local branch whose
name does not exist verbatim on the remote — including branches whose upstream
is correctly configured, which makes the failure look arbitrary.

Resolve in order instead: an already remote-qualified ref, the branch's
configured upstream, `<fallbackRemote>/<refName>`, then the local branch. The
local fallback matches what `GitManager.resolveBaseRangeRef` already does with
this same resolver. `remoteRefName` is now null in that case, so callers can
tell a remote base from a local one.

When nothing resolves, fail with a message naming the ref that was looked for
rather than passing git's stderr through.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant