Repository navigation
[Bug]: Fork checkouts resolve "the primary remote" four different ways, so features disagree on the repository #17314
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- Clone a fork. Set
originto<you>/<repo>and addupstreamfor<org>/<repo>. - Make local
maintrackupstream/main. Optionally rungh repo set-default. - Add the checkout as a T3 project and use it normally: start worktrees, open PRs, enable Automatically pull and Delete merged worktrees.
Expected behavior
Every feature agrees on which remote is the source repository (where main comes from and PRs land) and which is the fork (where branches are pushed).
Actual behavior
Each feature answers that question with its own rule, so in one checkout they point at different repositories:
| Feature | Rule at 580948708b |
Picks in a fork checkout | Report |
|---|---|---|---|
| Project identity | pickPrimaryRemote: upstream, then origin |
source repository | #15023 |
gh base repository |
selectGitHubBaseRepository: the gh-resolved mark, then gh's own name ranking |
whatever gh repo set-default chose |
#15023, #7382 |
Merged-worktree cleanup, GitManager, Bitbucket |
resolvePrimaryRemoteName: origin if it exists, else the first remote |
fork | #14843 |
| Start from origin | the literal "origin" in ThreadLaunchService and WorktreeMcpService |
fork | #17312 |
| Automatically pull | git pull --ff-only, so the branch's configured tracking remote |
source repository | (works) |
The visible results are the four linked reports: fork PRs that do not link to the thread, PR creation that fails, merged worktrees that are never cleaned up, and new worktrees that start from a stale base. Each one is a caller asking for "the primary remote" and getting an answer that suits a different job.
A fork checkout has two remotes with different jobs, so one "primary remote" cannot be right for all callers:
- a base remote: where the default branch comes from and where PRs are opened;
- a push remote: where the user's branches go.
This report is the common cause, not a fifth symptom. I found it by reading main at 580948708b; the individual symptoms carry their own runtime evidence in the linked issues.
Suggested direction
One place that resolves both roles for a checkout, used by all of the callers above. The signals already in use would feed it: the gh-resolved mark, the default branch's tracking remote, remote.pushDefault / branch.<name>.pushRemote, then the remote names as a fallback. A checkout with a single remote resolves both roles to it, so nothing changes outside fork setups.
I am not asking for this to be implemented as described. The decision I would like is whether the four reports should keep being fixed one caller at a time, or share one rule.
Two things to weigh:
- fix(server): repository identity prefers the gh default remote #14291 fixes project identity alone by teaching
RepositoryIdentityResolverthegh-resolvedrule. It would be the natural first consumer of a shared resolver, or it can land first and be folded in later. - Merged-worktree cleanup retains upstream-merged branches in fork checkouts until the fork is synced #14843 records that the triage on Support merged-worktree cleanup for PRs targeting release branches or stack parents #14758 kept a new target-repository resolver out of that issue's scope. This report does not reopen Support merged-worktree cleanup for PRs targeting release branches or stack parents #14758; it asks the question on its own.
Impact
Major degradation or frequent failure
Version or commit
main at 580948708b66f2a971199670f76cb6b43119e9a4 (2026-10-08)
Environment
Linux x86_64, Git 2.43.0. Fork checkout with origin = fork, upstream = source repository, branch.main.remote=upstream, remote.upstream.gh-resolved=base.
Workaround
Keep the fork's default branch synced with the source repository (gh repo sync). That hides the worktree-base and cleanup symptoms. It does not help #15023 or #7382.
Prepared with Claude Opus 5.5 through the Claude Code harness in T3 Code.
Activity
benthecarman commentedon Oct 8, 2026
+1 for one shared rule. I hit the "Start from origin" symptom (#17312) in a standard fork checkout (
origin= fork,upstream= source). Project identity already treatsupstreamas the source repository, and worktree creation treatsoriginas the source, so they disagree. A single base/push resolver would remove that mismatch. Any fallback that checks the remote names should treatupstreamas the base remote, so that setups that don't trackupstreamstill work.