From 5c6db18444a81feefe8a703cfa6e92b2415d7581 Mon Sep 17 00:00:00 2001 From: David Foy Date: Wed, 20 May 2026 14:48:43 +0900 Subject: [PATCH] fix(land-and-deploy): detect merged PR after gh failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After ANY non-zero exit from `gh pr merge`, query authoritative GitHub PR state before retrying or stopping. Replaces the narrow worktree-specific trigger with a universal post-failure invariant. Three outcome branches: - MERGED: success path. Capture SHA, offer (non-force) cleanup of clearly-stale worktrees with no uncommitted work, continue to CI detection. - OPEN + auto-merge enabled: queue-wait path (existing merge-queue handling). - OPEN (no auto-merge) or CLOSED: genuine failure, surface both errors, STOP. Hard rule: never call `gh pr merge` twice after a failure. Server state is authoritative. Worktree cleanup is non-destructive — candidates-only, no --force, defers to user. Related: cli/cli#3442, cli/cli#13380. --- land-and-deploy/SKILL.md | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/land-and-deploy/SKILL.md b/land-and-deploy/SKILL.md index b58ec23164..ef7497cd4b 100644 --- a/land-and-deploy/SKILL.md +++ b/land-and-deploy/SKILL.md @@ -1455,6 +1455,49 @@ If direct merge succeeds: record `MERGE_PATH=direct`. Tell the user: "PR merged If the merge fails with a permission error: **STOP.** "I don't have permission to merge this PR. You'll need a maintainer to merge it, or check your repo's branch protection rules." +### 4a-postfail: Post-failure PR-state check + +**Universal invariant:** after ANY non-zero exit from `gh pr merge`, query authoritative PR state before retrying or stopping. Do NOT retry `gh pr merge`. Related: cli/cli#3442, cli/cli#13380. + +```bash +gh pr view --json state,mergeCommit,mergedAt,mergedBy +``` + +**If `state == "MERGED"`:** + +The server-side merge succeeded (possibly completed before the local cleanup phase failed, or a concurrent merge landed). Tell the user: "PR is merged on GitHub." (Do NOT say "the merge succeeded" — this handles the concurrent-merge case.) + +Capture merge SHA: +```bash +gh pr view --json mergeCommit -q .mergeCommit.oid +``` + +Worktree cleanup — non-destructive, candidate-based: +```bash +git worktree list --porcelain +``` +Identify candidates: a worktree is stale if (a) it is checked out on the base branch, AND (b) it is not the user's current main working tree, AND (c) `git status --porcelain` inside it is empty (no uncommitted work). + +- For each clean candidate: OFFER to remove it. Say: "There's a stale worktree at `` checked out on `` with no uncommitted work. Remove it?" Remove only if user confirms (`git worktree remove && git worktree prune`). +- If any candidate has uncommitted work: list the files, tell the user, and STOP worktree cleanup without removing anything. +- Do NOT use `--force`. Do NOT remove the user's primary working tree. + +Record `MERGE_PATH=direct`, then continue to §4a (CI auto-deploy detection). + +**If `state == "OPEN"`:** + +Check whether auto-merge is enabled: +```bash +gh pr view --json autoMergeRequest -q .autoMergeRequest +``` + +- If non-null: auto-merge is enabled or merge queue is in use. The open state is expected — proceed to §4a's merge-queue wait path. +- If null: genuine failure. Surface both errors — the `gh pr merge` stderr AND the current PR open state — then **STOP**. + +**If `state == "CLOSED"`:** PR was closed without merging. **STOP.** + +**Hard rule: never call `gh pr merge` a second time** after a non-zero exit. Server state is authoritative. + ### 4a: Merge queue detection and messaging If `MERGE_PATH=auto` and the PR state does not immediately become `MERGED`, the PR is