Skip to content

fix: prune pool-maintenance reaps live unborn-branch upstream config - #33

Merged
zheng-weihao merged 1 commit into
orbcli:mainfrom
zheng-weihao:prune-guard-unborn-upstream
Aug 13, 2026
Merged

fix: prune pool-maintenance reaps live unborn-branch upstream config#33
zheng-weihao merged 1 commit into
orbcli:mainfrom
zheng-weihao:prune-guard-unborn-upstream

Conversation

@zheng-weihao

Copy link
Copy Markdown
Member

Problem

orbit_prune_repo_maintenance judged an orphan branch.<name>.* section by ref existence alone (rev-parse refs/heads/<name>). An empty repo's orbit add bootstraps an orphan worktree whose scoped branch is unborn — registered in the worktree list, upstream config wired, but no ref — so any untargeted orbit prune's closing sweep reaped the live worktree's push routing. The first git push after authoring the first commit then fails 128 (has no upstream branch), breaking scoped mode's wired-up-front promise. Recovery paths are all poor: git's own hint (git push -u origin ws/<ws>/main) publishes the internal prefixed branch name, and the correct repair (hand-editing branch.*) is one the skill's safety rules forbid.

Repro: orbit clone <empty-repo>orbit add → bare orbit prune → commit → git push → 128.

Fix

Collect branches checked out in worktrees from the same worktree list --porcelain pass (unborn branches are listed there, ref or no ref) and skip their config sections — mirroring orbit_prune_raw_residue's checked-out guard. Two refinements:

  • The pool's own checkout does not protect. For an empty repo, porcelain lists the pool checkout under an unborn main; its clone-written branch.<default>.* is bookkeeping residue, not push routing. Exempting the pool (via porcelain worktree→branch line grouping) keeps the previously-correct reap behavior unchanged.
  • pwd -P normalization before path comparison. Porcelain prints physical paths (macOS /var/private/var); a logical $repo never matched the pool's own worktree line, so the pre-existing registration-side pool exemption was latently dead (saved only by the -e fallback). The logical basename is kept for the summary line so a symlinked pool dir stays consistent with its group header.

docs/spec-warnings.md: the orphan-config row now defines the boundary — gone = no ref AND checked out in no non-pool worktree; the pool's own checkout never counts.

Tests

tests/09_prune.bats empty-repo regression pair:

  1. bare prune after orbit add keeps branch.ws/dev/main.* while still reaping the pool's branch.main.*
  2. doneprune closes the lifecycle: D1 removes the worktree, the guard self-limits, both sections reaped

Full suite: 366/366 passing.

…— guard checked-out branches, exempt pool's own checkout

orbit_prune_repo_maintenance judged an orphan branch.<name>.* section by
ref existence alone (rev-parse refs/heads/<name>). An empty repo's
'orbit add' bootstraps an orphan worktree whose scoped branch is UNBORN —
registered in the worktree list, upstream config wired, but no ref — so
any untargeted prune's closing sweep reaped the live worktree's push
routing. First 'git push' after authoring the first commit then failed
128 ('has no upstream branch'), breaking scoped mode's
wired-up-front promise; git's own hint (-u origin ws/<ws>/main) would
publish the internal prefixed name, and the correct repair (hand-editing
branch.*) is one the skill forbids.

Guard: collect branches checked out in worktrees from the same
worktree-list --porcelain pass (unborn branches are listed there, ref or
no ref) and skip their sections — mirroring orbit_prune_raw_residue's
checked-out guard. Two refinements:

- The pool's own checkout does NOT protect: porcelain lists it under an
  unborn 'main' for an empty repo, and its clone-written
  branch.<default>.* is bookkeeping residue, not push routing — exempting
  the pool keeps the previously-correct reap behavior unchanged (the
  branch line is attributed to its worktree line via porcelain grouping).
- Normalize $repo with pwd -P before any path comparison: porcelain
  prints physical paths (macOS /var -> /private/var), so a logical $repo
  never matched the pool's own worktree line — the pre-existing
  registration-side pool exemption was latently dead, saved only by the
  -e fallback. The logical basename is kept for the summary line so a
  symlinked pool dir's line stays consistent with its group header.

spec-warnings.md: the orphan-config row now defines the boundary —
'gone' = no ref AND checked out in no non-pool worktree; the pool's own
checkout never counts.

tests/09_prune.bats: empty-repo regression pair — (1) bare prune after
'orbit add' keeps the worktree's branch.ws/dev/main.* while still
reaping the pool's branch.main.*; (2) done -> prune closes the
lifecycle: D1 removes the worktree, the guard self-limits, both sections
reaped.

Signed-off-by: zheng-weihao <zheng-weihao@outlook.com>
@zheng-weihao
zheng-weihao merged commit 84837d2 into orbcli:main Aug 13, 2026
2 checks passed
@zheng-weihao
zheng-weihao deleted the prune-guard-unborn-upstream branch August 24, 2026 11:08
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