Skip to content

fix: reconcile fetch refspecs with remote — heal bare git fetch after branch deletion - #21

Merged
zheng-weihao merged 2 commits into
mainfrom
fix-stale-fetch-refspec
Jul 28, 2026
Merged

zheng-weihao merged 2 commits into
mainfrom
fix-stale-fetch-refspec

Conversation

@zheng-weihao

@zheng-weihao zheng-weihao commented Jul 24, 2026 •

Copy link
Copy Markdown
Member

Problem

An exact fetch refspec pointing at a branch the remote no longer has makes every bare git fetch fail with fatal: couldn't find remote ref — in every worktree of the pool. Two sources produced such entries:

  1. Residue: orbit_add_fetch_refspec entries outlive their remote branch (typical: auto-deleted on PR merge). Nothing ever removed them.
  2. Pre-registration (found by dogfooding this fix): switch -c registered a refspec for the not-yet-pushed branch, breaking every bare fetch between switch and first push — the fix branch itself broke the pool's fetch while this PR was being written.

Fix

Every orbit command that already fetches opportunistically — orbit sync, orbit info, the orbit context --startup reignite block — plus orbit prune now reconciles fetch refspecs with the remote first (one ls-remote per repo), then fetches bare (git fetch origin):

  • Remove entries whose remote branch is gone, along with their stale refs/remotes/origin/<branch> ref. → removed stale fetch refspec: <branch>
  • Register entries for locally-tracked branches that now exist remotely (scoped/raw branch pushed since last run); the bare fetch materializes the tracking ref immediately. → added fetch refspec: <branch>

Changes print on stderr (report commands) so stdout/JSON shapes are untouched; prune --dry-run reports would remove/add ....

Push-time UX preserved without the broken window: switch -c no longer pre-registers (the branch doesn't exist remotely yet — the refspec is the bug), but it still wires the upstream config up front, so push/pull work immediately; the tracking ref materializes at the first fetching command after the push — session start, orbit info, or orbit sync — or instantly via git fetch origin <branch>. (A git pre-push hook could materialize at push time, but orbit does not install git hooks into pool repos — it would clash with repos' own hook setups.)

Supporting details:

  • Removal rebuilds the value list for exact match — git config --unset treats the refspec's leading + as an invalid regex, and --fixed-value needs git ≥ 2.26 (orbit supports ≥ 2.20).
  • Reconciliation is skipped entirely when the remote is unreachable, so unverifiable refspecs are never removed by mistake.
  • Bare orbit context (cruise block) and orbit status stay purely local (no fetch, no reconcile).

Verified end-to-end on a live pool: planted residue → bare fetch fails → orbit sync removes it → bare fetch works; and the pre-registration residue left by this branch's own switch -c was cleaned the same way.

Tests

  • 09_prune: removes remote-deleted residue (asserts bare fetch broken before, healed after), --dry-run reports without mutating, removes legacy pre-registered entries while keeping the local branch
  • 18_sync: same removal via sync, keeps live refspecs
  • 06_worktree_integration: switch -c does not pre-register; push → orbit sync registers the refspec and materializes origin/<branch>
  • 12_context: push → session-start reignite registers + materializes (the agent-facing path)

Full suite: 215/215 ok, make lint clean.

… branch deletion

An exact fetch refspec pointing at a branch the remote no longer has makes
every bare git fetch fail with "couldn't find remote ref" in every worktree
of the pool. Two sources produced such entries:

- orbit_add_fetch_refspec entries outliving their remote branch (typical:
  auto-deleted on PR merge) — nothing ever removed them
- switch -c pre-registered a refspec for the not-yet-pushed branch, breaking
  every bare fetch between switch and first push (found by dogfooding)

orbit sync / prune now reconcile refspecs with the remote (single ls-remote):
remove entries whose remote branch is gone (plus their stale tracking refs),
register entries for locally-tracked branches that now exist remotely so
their refs materialize. switch -c no longer pre-registers; sync fetches bare
so newly registered refs materialize immediately. Removal uses an exact-match
list rebuild (git config --unset treats the refspec's leading + as a regex;
--fixed-value needs git >= 2.26). Reconciliation is skipped when the remote
is unreachable.

Signed-off-by: zheng-weihao <zheng-weihao@outlook.com>
…-time tracking UX

switch -c no longer pre-registers (previous commit), but pre-registration
existed to make the first push materialize the tracking ref immediately.
Restore that experience without the fetch-breaking window: orbit info and
the context --startup reignite block already fetched opportunistically —
they now reconcile refspecs first and fetch bare, so a scoped/raw branch
shows remote tracking from the first session start (or info/sync run) after
its push. Changes are announced on stderr; stdout report/JSON shapes are
untouched. cruise block and orbit status stay purely local.

Signed-off-by: zheng-weihao <zheng-weihao@outlook.com>
@zheng-weihao
zheng-weihao merged commit 5786bb6 into main Jul 28, 2026
2 checks passed
@zheng-weihao
zheng-weihao deleted the fix-stale-fetch-refspec branch July 28, 2026 03:18
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