Skip to content

fix(actions): make hub-side merge idempotent for already-integrated branches - #573

Merged
jlong merged 1 commit into
mainfrom
jlong/actions-make-the-merge-action-idempotent-so-an-already-merged-branch-advances-instead-of-stranding-the-card
Aug 16, 2026
Merged

fix(actions): make hub-side merge idempotent for already-integrated branches#573
jlong merged 1 commit into
mainfrom
jlong/actions-make-the-merge-action-idempotent-so-an-already-merged-branch-advances-instead-of-stranding-the-card

Conversation

@jlong

@jlong jlong commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Task

Defense-in-depth fix that protects every project regardless of orchestrator instructions. Complements the instruction-port task zen-finalize-merges-via-the-review-done-transition-not-zen-pr-merge-... (which stops the orchestrator from double-merging in the first place). This task makes the double-merge harmless if it ever happens again.

Current Behavior

The workflow merge action (used by transitions like review -> done: [merge, delete_branch]) does a hub-side squash-merge of the task branch into the base and commits. If the branch's content is ALREADY on the base (e.g. a prior zen pr-merge, or a human already merged the PR), the action fails two ways and leaves the card stuck in its pre-merge status:

  • Branch missing on origin (already merged + deleted): merge for <task> failed ... branch <b> is not on origin — run the push_branch action first.
  • Branch re-pushed but content already in base: git -C /tmp/shelbi-merge-... commit -m "shelbi: merge <task> from <branch>" exits 1 (nothing to commit → empty squash), so the transition aborts and the card never reaches done even though the work shipped.

Reproduced 2026-08-16 on task installer-suppress-cosmetic-launchd-boot-out-... (PR #570) after a zen pr-merge had already landed the squash commit 192cf32 on main. Recovery required hand-editing the workflow to drop the merge action.

Expected Behavior

The merge action is idempotent. Before merging it checks whether the branch is already integrated into the base — either the branch tip's content is already present on the base (no diff to merge / empty squash), or the branch's PR is already in the MERGED state. In that case the action treats the merge as a satisfied no-op and lets the transition proceed (so delete_branch runs and the card advances to done), instead of erroring.

Technical Details

Likely in the actions layer (crates/.../actionsmerge primitive) and/or the task-move transition runner. Suggested checks before attempting the hub-side merge:

  • If a PR for the branch exists and is MERGED, treat merge as done (optionally reconcile: ensure the base already contains the branch content) and continue the transition.
  • Else, if merging the branch into base produces no changes (empty diff / empty squash), treat as already-merged no-op and continue rather than failing on the empty commit.
  • Only genuinely-unmerged branches with real changes take the normal merge path.

Bonus (separate consideration, note if out of scope): when an open PR exists for the branch, the transition merge could prefer gh pr merge --squash so the PR shows as MERGED rather than being closed-unmerged by the branch deletion.

Acceptance Criteria

  • Moving a task from review to done when its branch is already merged into the base advances the card to done (runs remaining actions like delete_branch) instead of failing.
  • The "branch not on origin (already merged + deleted)" case advances the card rather than demanding push_branch.
  • The "branch re-pushed, content already in base" case does not fail on an empty/nothing-to-commit squash.
  • A genuinely unmerged branch with real changes still merges normally (no regression).
  • Behavior is workflow-agnostic (applies to any workflow whose transition uses the merge action).

Auto-opened by Shelbi — review at: /Users/jlong/.shelbi/projects/shelbi/tasks/actions-make-the-merge-action-idempotent-so-an-already-merged-branch-advances-instead-of-stranding-the-card.md

…ranches

Defense-in-depth backstop so a double-merge is harmless even with no PR to
probe. Complements the already-landed `AlreadyMerged` path (which advances a
`review -> done` transition when GitHub reports the branch's PR already
MERGED): that path needs a visible merged PR. This adds the PR-less case —
the branch's content is already on the base but no merged PR records it (a
purely-local project with no `gh`, or content that landed under another SHA).

Previously the hub-side fetch path failed two ways and stranded the card:
- "no commits beyond target" -> hard error.
- branch re-pushed but content already in base -> empty `git merge --squash`
  stages nothing, so `git commit` exits 1 and the transition aborts.

`merge_hub_side` now returns `HubMerge::{Merged(sha), AlreadyIntegrated}`. It
reports `AlreadyIntegrated` when the branch is an ancestor of the target
(no commits beyond) or when the squash stages nothing, instead of erroring.
`merge()` maps that to a new `MergeOutcome::AlreadyIntegrated { target }`
no-op variant so the transition proceeds (delete_branch runs, the card
advances). Wire format: `already-integrated:<target>`.

Key decisions (not asked about):
- Kept the existing "branch not on origin -> run push_branch first" error.
  A missing branch with no merged PR is a genuinely different situation
  (never pushed) from merged+deleted; the AlreadyMerged path already advances
  the real merged+deleted case via its merged-PR probe, so this path only
  fires for a branch that never reached origin.
- `AlreadyIntegrated` carries no PR number (unlike `AlreadyMerged`): this
  path is reached only after both the open- and merged-PR probes came back
  empty, so there is no PR to record.
- Rebase note: an earlier version of this commit also added a merged-PR probe
  and an `AlreadyIntegrated { pr }` field; that overlapped with `AlreadyMerged`
  which landed on main first, so it was dropped in favor of main's richer
  MergedPr lookup. This commit is now only the hub-side backstop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
shelbi Ready Ready Preview Aug 16, 2026 9:38pm

Request Review

@jlong
jlong merged commit 9b03ae5 into main Aug 16, 2026
3 checks passed
@jlong
jlong deleted the jlong/actions-make-the-merge-action-idempotent-so-an-already-merged-branch-advances-instead-of-stranding-the-card branch August 16, 2026 21:40
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