fix(land-and-deploy): MERGED recovery reconciles and reports remote-branch cleanup

Step 4's merge commands carry --delete-branch, and the success path tells the user 'The branch has been cleaned up.' When gh exits non-zero AFTER GitHub already merged (routine in worktree layouts: gh's local cleanup runs git checkout <base> and fails), the §4a-postfail MERGED recovery re-established everything EXCEPT the branch deletion — and said nothing about it, so the discrepancy was invisible. The MERGED path now reconciles: git ls-remote --heads distinguishes branch-already-gone (exit 0, empty → 'already cleaned up', idempotent on re-runs) from branch-survived (offer confirm-first deletion, matching the section's worktree posture; -d not -D for any local branch) from check-itself-failed (non-zero exit → 'couldn't verify', skip the offer — never read a failed check as a clean branch).

Template + regenerated SKILL.md + test extensions land in one commit (the md-sync assertion goes red otherwise). Regression assertions (fail on v1.68.3.0: no delete-branch reconciliation existed in test/ at all) pin the ls-remote check, the confirm-first delete, and the absent-vs-failed distinction.

Fixes #2656
This commit is contained in:
Garry Tan
2026-08-22 02:12:43 +00:00
parent 88209d542a
commit e01ee8fe86
3 changed files with 46 additions and 0 deletions
+20
View File
@@ -87,6 +87,26 @@ describe("PR #1620 §4a-postfail in land-and-deploy template", () => {
expect(body).toMatch(/continue to §4a/);
});
// #2656: the failed merge carried --delete-branch; the recovery path must
// reconcile the remote branch instead of silently dropping that half.
test("MERGED branch reconciles the remote branch (ls-remote, confirm-first delete)", () => {
const body = readTmpl();
expect(body).toMatch(/git ls-remote --heads origin "\$BRANCH"/);
expect(body).toMatch(/gh pr view --json headRefName -q \.headRefName/);
expect(body).toMatch(/git push origin --delete "\$BRANCH"/);
// Confirm-first: deletion is offered, never unilateral.
expect(body).toMatch(/Delete it\?/);
});
test("MERGED branch reconciliation distinguishes branch-absent from check-failed", () => {
const body = readTmpl();
// exit 0 + empty output = already clean (idempotent re-runs)...
expect(body).toMatch(/already been cleaned up/);
// ...non-zero exit = unknown state, never read as a clean branch.
expect(body).toMatch(/Couldn't verify remote branch state/);
expect(body).toMatch(/never read a failed check as a clean branch/);
});
test("OPEN branch checks autoMergeRequest before treating as failure", () => {
const body = readTmpl();
expect(body).toMatch(/gh pr view --json autoMergeRequest/);