merge_pr.py hard-resets whatever branch is checked out when it finishes, with no guard nf-5mxd ← Beads

closed priority 2 task unassigned activesession:merge-reset-guard

Twinned from https://github.com/cfsh/ninefold/issues/795 by tools/beads/import_github.py's reconcile pass.

Filed unclaimed by nf-dev-rev-pass-gate after hitting it on #786/#788. Not claimed, not labelled.

What happens

merge_pr.py owns the worktree for the minutes it runs: it checks out the PR head to run the presubmit, and after the merge it syncs local main with a git reset --hard. That reset lands on whatever branch is checked out at that moment — not necessarily the one it started on.

So a session that runs merge_pr.py <n> in the background (which CLAUDE.md effectively requires — *"⏱ It takes minutes … a 2-minute timeout kills the gate after the merge landed"*) and then does anything with git switch in the same worktree gets that other branch hard-reset to main.

Measured here, 2026-08-10:

``` 03:41:30 checkout: moving from feat/rev-pass-gate to chore/delete-dispatcher-cron (at fec4b99) 03:42:11 reset: moving to origin/main → 007a63a ```

chore/delete-dispatcher-cron — the branch behind open PR #788 — lost nine commits from its ref, including one that had never been pushed. The post-merge UI gate then reported itself as UI gate: chore/delete-dispatcher-cron @ 007a63a, naming a branch that had nothing to do with the merge.

Why it is worth a guard rather than a note
Candidate fixes, cheapest first
  1. Record git rev-parse --abbrev-ref HEAD at the start and refuse to reset if HEAD moved. Three lines, and it converts silent damage into an informative refusal — the shape the rest of the file already uses. It cannot fix a branch switched *and* switched back, but that is not the realistic case.
  2. Reset by ref, not by checkoutgit fetch origin main:main updates local main without touching the working tree at all, and is what the sync actually wants. Needs care when main *is* checked out.
  3. Say it in the docstring and notes/merging.md — necessary either way, and not sufficient on its own.

⚠️ Note the interaction with EXIT-trap restore: the tool restores the branch it checked out for the presubmit, which is why the hazard is specifically the *post-merge sync* and not the presubmit stage.

Dependencies

None.

Comments

No comments.

Add a comment