PR #142 reworks PR checkout (workmux add --pr N) to avoid creating
fork-<owner> remotes. Instead of fetching from the fork and setting up a named
remote for tracking, it fetches refs/pull/<N>/head from the base repo and
stores the head repo URL directly in branch.<name>.remote.
This document explains why that approach breaks multiple core workmux and git workflows.
The PR replaces the existing flow:
1. Call gh API to get PR details
2. Create fork remote (e.g., fork-mic92)
3. Fetch from fork remote
4. Create branch tracking fork remote
With:
1. Call gh API to get PR details
2. Fetch refs/pull/N/head from origin
3. Create branch
4. Set branch.<name>.remote = [email protected]:head-owner/repo.git (RAW URL)
5. Set branch.<name>.merge = refs/heads/<branch>
The refs/pull/<N>/head fetch is correct and solves a real problem (deleted
head branches). But storing a raw URL in branch.remote violates Git's data
model and breaks tooling across the stack.
Severity: HIGH File: src/git/branch.rs:286-304
Workmux's remove --gone feature uses
git for-each-ref --format="%(refname:short)|%(upstream:track)" to find
branches whose upstream has been deleted. When branch.remote is a URL, Git
returns an empty track string instead of [gone].
Test evidence:
# URL-valued remote (PR #142 behavior):
$ git for-each-ref --format="%(refname:short)|%(upstream:track)" refs/heads
main|
pr-branch| <-- empty, not [gone]
# Named remote (existing behavior):
$ git for-each-ref --format="%(refname:short)|%(upstream:track)" refs/heads
main|
pr-branch|[gone] <-- correctly detectedThis means PR branches will accumulate forever. Users will have no automatic way
to discover stale PR checkouts. The remove --gone command becomes useless for
the exact branches it should be most useful for.
Severity: HIGH
The PR hardcodes SSH transport for the tracking URL:
let head_url = format!("git@{}:{}/{}.git", host, head_owner, head_repo.name);But the fetch path uses HTTPS as fallback. Users who:
- Cloned the repo via HTTPS
- Use credential helpers (not SSH keys)
- Are behind corporate proxies that block SSH
will get broken push/pull after checkout:
$ git config branch.feature.remote "[email protected]:forkowner/testrepo.git"
$ GIT_SSH_COMMAND="ssh -o BatchMode=yes" git pull
ERROR: Repository not found.
fatal: Could not read from remote repository.The PR does not infer the user's preferred transport from origin. It assumes
everyone uses SSH for forks.
Severity: HIGH File: src/git/remote.rs (PR introduces
find_remote_for)
The PR's find_remote_for() matches remotes only on owner and optionally
repo:
if !provider.owner().eq_ignore_ascii_case(owner) { continue; }
if let Some(repo) = repo_name
&& !provider.repo().eq_ignore_ascii_case(repo)
{
continue;
}It never checks host. A remote pointing to gitlab.com/user/repo would match
a lookup for github.com/user/repo. The owner:branch syntax passes None for
repo_name, making it match any remote with that owner regardless of host or
repository.
This could cause workmux add owner:branch to silently reuse a remote from the
wrong forge, sending pushes to the wrong place.
Severity: MEDIUM File: src/workflow/pr.rs (PR's resolve_pr_ref)
If a local branch already exists, the PR returns it without fetching or verifying:
if git::branch_exists(&local_branch)? {
return Ok(PrCheckoutResult { local_branch });
}This means workmux add --pr 123 can attach a worktree to a stale branch that
has nothing to do with the PR. If the user previously created a feature branch
locally, then runs workmux add --pr 456 where PR 456's head branch is also
named feature, workmux silently uses the local branch without fetching PR
456's code.
Severity: MEDIUM Files: src/command/add.rs, src/workflow/create.rs
The PR writes to .git/config in resolve_pr_ref(), which is called from
command/add.rs before plan.execute(). But the GitConfigLock is only
acquired inside workflow/create.rs.
add.rs:320 resolve_pr_ref() -> writes config
...
add.rs:497 plan.execute() -> create.rs:349 GitConfigLock::acquire()
Concurrent workmux add --pr commands will race on git's config.lock file,
causing one to fail with "could not lock config file."
Severity: MEDIUM
Cross-repo PRs prefix the local branch with the owner (e.g.,
forkowner-feature) while the upstream tracks feature. With
push.default=simple (Git default since 2.0):
$ git push
fatal: The upstream branch of your current branch does not match
the name of your current branch. To push to the upstream branch
on the remote, use
git push fork HEAD:featureThis only appears after the user makes a commit. Before that, git says "Everything up-to-date," hiding the problem. The user discovers they cannot push only after they've done work.
Severity: MEDIUM (UX degradation)
Git porcelain commands assume branch.remote is a remote name. When it's a URL:
git branch -vvshows no upstream column for PR branchesgit status --porcelain=v2 --branchomits upstream tracking info- Shell prompts that show upstream status break
This makes PR branches feel "second-class" in normal Git workflows.
Severity: LOW File: Sandbox excluded_files implementation
The mount spec type=bind,source=/dev/null,target=/path,readonly uses CSV-like
parsing. A path containing a comma would be mis-parsed. While paths with commas
are rare, this is a latent bug in the sandbox feature.
The PR leaves two different remote management strategies in the codebase:
--pr: URL-based tracking, no named remoteowner:branch: Named remotes viaensure_fork_remote()
This means users get completely different behavior depending on which syntax they use. Both paths solve the same problem (getting code from a fork) but with incompatible implementations.
One might ask: why not just special-case URL remotes in get_gone_branches()?
Because the problem is not localized. URL-valued branch.remote breaks:
- Git's upstream tracking (
%(upstream:track)) git branch -vvgit status --porcelain=v2 --branchpush.default=simple- Any shell prompt that reads upstream state
- Any future workmux feature that assumes
branch.remoteis a remote name
Patching each symptom individually creates a maintenance burden and still leaves
Git in an invalid state. The fix is to not write URLs into branch.remote in
the first place.
The refs/pull/<N>/head fetch mechanism in PR #142 is correct and valuable. But
the URL-based tracking approach is architecturally wrong. It violates Git's data
model, breaks workmux's own cleanup features, and degrades the standard Git UX
for PR branches.
The right fix keeps the refs/pull/<N>/head fetch but uses named remotes for
tracking, either:
- Per-PR scoped remotes (e.g.,
wm-pr-123) with proper refspecs, or - Shared fork remotes with strict
host+owner+repomatching, inheriting the user's transport protocol.
Both approaches preserve Git's upstream tracking semantics and fix the original problem without introducing new breakage.