Skip to content

diff sync regression: EnsureClone re-adds legacy refs/heads fetch rule that breaks linked worktrees #169

@mariusvniekerk

Description

@mariusvniekerk

Summary

On April 19, 2026, diff sync for wesm/middleman started failing because the shared cached clone under ~/.config/middleman/clones/... still had the legacy fetch refspec:

+refs/heads/*:refs/heads/*

That clone is also used as the source repository for linked worktrees. Once a linked worktree has a local branch checked out, git fetch in the shared clone refuses to update that branch.

User-visible symptom

The UI shows:

  • Diff data is unavailable: the local repository clone could not be prepared.

The sync log shows the underlying git error:

2026/04/19 10:29:12 WARN diff sync failed during sync PR owner=wesm name=middleman number=166 code=clone_unavailable err="ensure bare clone for #166: git fetch: exit status 128: fatal: refusing to fetch into branch 'refs/heads/issue-workspace' checked out at '/Users/mariusvniekerk/.config/middleman/worktrees/github.com/wesm/middleman/pr-161'"

What I observed locally

Shared clone:

  • ~/.config/middleman/clones/github.com/wesm/middleman.git

Linked worktree:

  • ~/.config/middleman/worktrees/github.com/wesm/middleman/pr-161

That worktree had this branch checked out:

  • refs/heads/issue-workspace

The shared clone's fetch config contained all of these entries:

+refs/pull/*/head:refs/pull/*/head
+refs/heads/*:refs/remotes/origin/*
+refs/heads/*:refs/heads/*

The last entry is enough to break fetch in the presence of linked worktrees.

Why this looks like a regression

I manually removed the legacy refspec from the cached clone and immediately verified that fetch succeeded:

git -C ~/.config/middleman/clones/github.com/wesm/middleman.git \
  config --unset-all --fixed-value remote.origin.fetch \
  '+refs/heads/*:refs/heads/*'

git -C ~/.config/middleman/clones/github.com/wesm/middleman.git fetch --prune origin

That fetch completed successfully and updated origin/issue-workspace plus the relevant PR refs.

After that, a later sync from the running local middleman process reintroduced the exact same legacy refspec into the clone config, and the failure returned.

That means this is not just stale local state. The current EnsureClone logic appears to still treat +refs/heads/*:refs/heads/* as a desired/default refspec and re-add it for existing clones.

Reproduction sketch

  1. Use a middleman shared clone that configures remote.origin.fetch with +refs/heads/*:refs/heads/*.
  2. Create a linked worktree from that clone.
  3. Check out a local branch in the worktree, for example issue-workspace.
  4. Advance the corresponding remote branch.
  5. Run diff sync or git fetch --prune origin in the shared clone.
  6. Git fails with:
fatal: refusing to fetch into branch 'refs/heads/issue-workspace' checked out at '.../pr-161'

Expected behavior

The shared clone used for diff sync should not fetch remote branches directly into refs/heads/* once it also owns linked worktrees.

Suggested fix

  • Stop treating +refs/heads/*:refs/heads/* as a desired/default fetch refspec.
  • Keep branch mirroring under refs/remotes/origin/* instead.
  • During clone preparation, actively remove the legacy refs/heads/* -> refs/heads/* refspec from existing clones so old caches migrate forward.

Additional impact observed

This also stranded workspace records in the local DB for workspaces that never finished creating successfully, because failed workspaces in this state did not have a delete path in the UI. I had to manually remove those stale error rows from middleman_workspaces in the local SQLite database.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions