Skip to content

Fix stuck PRs from incomplete conflict-resolution comment#32

Closed
Phlogistique wants to merge 1 commit into
mainfrom
fix-conflict-comment-incomplete-merge-sequence
Closed

Fix stuck PRs from incomplete conflict-resolution comment#32
Phlogistique wants to merge 1 commit into
mainfrom
fix-conflict-comment-incomplete-merge-sequence

Conversation

@Phlogistique

@Phlogistique Phlogistique commented May 29, 2026

Copy link
Copy Markdown
Collaborator

When updating a descendant branch after its parent PR was squash-merged hits a merge conflict, the bot posts a comment telling the author how to finish by hand. That comment listed only the ref that conflicted, so a human following it ran a single git merge and ended up with a branch still missing the parent-branch merge and the -s ours squash record — never mergeable into its new base, so it stayed stuck.

The sequence is the same whichever merge conflicts, so the comment now prints it in full: merge the parent branch, merge the pre-squash target, then record the squash with -s ours. This fixes the comment only; the author runs the steps by hand.

The e2e conflict scenario used to resolve by merging the base directly, so it never exercised the comment. It now follows the comment's commands verbatim, proving a human who follows them ends up mergeable.

Diagnosed on sensei #7987.

@Phlogistique
Phlogistique force-pushed the fix-conflict-comment-incomplete-merge-sequence branch 2 times, most recently from efb2c42 to a75b7da Compare May 29, 2026 14:40
When updating a descendant branch after its parent PR was squash-merged
hits a merge conflict, the bot posts a comment telling the author how to
finish by hand. That comment listed only the ref that conflicted, so a
human following it ran a single `git merge` and ended up with a branch
still missing the parent-branch merge and the `-s ours` squash record —
never mergeable into its new base, so it stayed stuck.

The sequence is the same whichever merge conflicts, so the comment now
prints it in full: merge the parent branch, merge the pre-squash target,
then record the squash with `-s ours`. This fixes the comment only; the
author runs the steps by hand.

The e2e conflict scenario used to resolve by merging the base directly,
so it never exercised the comment. It now follows the comment's commands
verbatim, proving a human who follows them ends up mergeable.

Diagnosed on sensei #7987.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Phlogistique
Phlogistique force-pushed the fix-conflict-comment-incomplete-merge-sequence branch from a75b7da to 3100b39 Compare May 29, 2026 14:44
Phlogistique added a commit that referenced this pull request May 29, 2026
Alternative to #32 for the same stuck-PR bug — pick one.

After a parent PR is squash-merged, updating a descendant branch runs three
merges: the parent branch, the target's pre-squash state, then the squash
recorded with `-s ours`. When the pre-squash merge conflicted, the action
commented and labeled without pushing anything, so the head was left missing
both the base merge and the squash record. The branch never became mergeable,
and because GitHub skips `pull_request` workflows on a PR that conflicts with
its base, the `synchronize` event that resumes the action never fired again —
permanently stuck.

This splits the work so the user only resolves the genuine conflict:

- When the base-branch merge is clean and only the pre-squash merge conflicts,
  push the base merge to the branch before commenting and labeling. The head
  stays a descendant of its base, so the PR stays mergeable and the resume
  trigger keeps firing. If the base merge itself conflicts there is nothing safe
  to pre-push, so the existing comment-and-label fallback runs.
- After the user resolves and pushes, continue_after_resolution records the
  squash with `-s ours` and pushes before retargeting. The synchronize payload
  is the child PR, so the squash commit and new target are reconstructed from
  the merged parent PR (its merge commit and base).

The conflict comment is unchanged: it already lists the genuine conflict.

Offline test covers both halves: the squash-merge run pushes the base merge
before the unchanged comment, and the follow-up run records the squash so the
branch ends up mergeable into the new target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phlogistique added a commit that referenced this pull request Jun 1, 2026
Alternative to #32 for the same stuck-PR bug. Pick one.

Background: when a parent PR is squash-merged, the action brings each
child branch up to date with three merges: the parent branch (the
base-branch merge), the trunk as it stood just before the squash landed
(`SQUASH_COMMIT~`), and the squash itself recorded with `-s ours` so the
child's diff against its new base stays clean. When the pre-squash trunk
merge conflicts, the action asks the user to resolve, and a follow-up
run triggered by the user's push finishes the job.

Two separate bugs left a conflicting child PR broken:

1. **The follow-up run never finished the job.** It removed the conflict
label, retargeted the PR, and deleted the old base branch, but it never
recorded the squash. The synthetic merge commit that keeps the child's
diff against its new base clean was never created, and because the label
was gone the action would never revisit the PR.
2. **Even with that fixed, the recipe could push the user into an
unrecoverable state.** The conflict comment asked the user to merge only
the conflicting ref, starting from a head that did not contain the
base-branch merge. When that merge is clean the head is clean against
its base going in, so the only thing that can make it conflict with the
base is the user's own resolution. If resolving the `SQUASH_COMMIT~`
conflict produced content that conflicts with the parent branch, the
user's push left the head conflicting with its base. GitHub does not run
`pull_request` workflows on a PR that conflicts with its base, so the
follow-up run never fired and the PR was stuck for good.

This PR fixes both:

- Push the base-branch merge before commenting, when it is clean. The
head then always contains it, so the parent branch is an ancestor and no
resolution can make the head conflict with its base. The PR stays
mergeable and the synchronize trigger keeps working. If the base merge
itself conflicts there is nothing safe to pre-push, so the existing
comment-and-label fallback runs.
- `continue_after_resolution` now records the squash with `-s ours` and
pushes before retargeting. The synchronize payload is the child PR, so
the squash commit and new target are reconstructed from the merged
parent PR (its merge commit and base).

Pushing the base merge leaves the user's local branch behind origin, so
the conflict comment now starts the recipe with `git pull --ff-only
origin <branch>`. Without it the user resolves on a stale head and the
final push is rejected as non-fast-forward. The comment still lists only
the genuine conflict to merge.

The e2e conflict scenario already triggers this case (clean base merge,
conflicting `SQUASH_COMMIT~` merge), so coverage lives there: it checks
the action pushed the base merge on top of the pre-conflict head and
that the comment asks only for the genuine conflict, then resolves by
following the comment verbatim (re-sync included) and checks
`continue_after_resolution` records the squash so the branch ends up
mergeable into the new target.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Phlogistique

Copy link
Copy Markdown
Collaborator Author

superseded by #33

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