Skip to content

Merge run post-merge steps: delete the Issue branch, close the issue, survive failures and Ctrl-C - #98

Merged
JacobStephens2 merged 4 commits into
mainfrom
issue-92
Sep 27, 2026
Merged

JacobStephens2 merged 4 commits into
mainfrom
issue-92

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

A Self-merge now ends with the Issue branch gone from origin and the issue closed. Once the merge has happened, nothing turns the Merge run into a Failed run. It builds on main's retry loop for failed merges (#97).

 implement
   repair_loop.run → loop
     ensure_pr_ready_and_mergeable
     if interrupted → Failed run
     github::merge(head)
+      gh failed but PR merged at exactly `head` → still a merge
+    merged → after_merge            # never fails the Run, ignores Ctrl-C
+      worktree.delete_from_origin   # already gone (GitHub auto-delete) → fine
+      close_unless_merge_does
+        base is default branch && PR links the issue for closing → leave it to GitHub
+        issue still open → gh issue close --comment "Closed by #<pr>, merged into <base> by a thirdshift Merge run."
+      each step: retried once if Ctrl-C killed its git/gh
+      on failure: error line, then `warning: …, so <do> it by hand: <command>`
+    → Ok(pr.url): exit 0, URL on stdout, "PR <url> is merged"
     failed → round_after_failed_merge (main, unchanged)

The fake gh gains:

  • issue close --comment and repo view --json defaultBranchRef
  • headRefOid and closingIssuesReferences on pr view
  • the test hooks gh fake after-merge, gh fake fails '<cmd> <sub>' and gh fake issue <n> <state>

Main's before-merge on-merge <times> hook keeps its name. github.rs gets a shared pr_view helper for its four gh pr view calls. The README documents the post-merge steps and the success-with-warnings outcome.

Evidence

  • Before (new tests/merge_run.rs against the first cut of the code): the tests for branch deletion, issue close, warnings and Ctrl-C after the merge failed, e.g. origin_log("issue-7") == None → left: Some(["Add feature", "Initial commit"]).
    After (branch merged with current main): cargo test passes every suite, with merge_run at 22/22, and cargo clippy --all-targets and cargo fmt --check are clean. The suite covers:
    • the branch is deleted on origin, or GitHub already deleted it, with no warning
    • into develop, thirdshift closes the issue with the comment. Into main, gh closes it and thirdshift makes no gh issue close call, even when GitHub hasn't closed it yet when thirdshift checks.
    • a failed deletion or close exits 0, prints the URL on stdout and a warning: line naming the manual command
    • SIGINT after the merge exits 0 as merged, with the steps done. SIGINT before it gives a Failed run.
    • a gh pr merge that errors after merging at the watched head counts as a merge. If it merged at another head, the Run is a Failed run.
    • main's failed-merge Repair tests still pass. One now checks that the fix reached main rather than reading issue-7 on origin, since that branch is deleted.

Merge Danger

Door: two-way

The code reverts cleanly. Runs made while it's live delete remote Issue branches and post issue comments. Those are one-way for the repos they touch, but they're what the issue asked for.

Blast Radius: Merge-runs

Only thirdshift merge changes. A plain Run never reaches this code. Each successful merge makes up to 3 extra gh calls.

Unaddressed findings

Standards

  • github::merge and delete_from_origin share a "try, then check whether it happened anyway" shape. Skipped. There are only two copies, in different modules, and each checks something different.
  • The manual-fix commands in the warnings repeat the argv of github::close_issue / delete_from_origin (Shotgun Surgery). Skipped. The hand-run commands differ on purpose: no --no-verify, and quoting for a shell. Deriving them from the internal calls would tie two different audiences together.
  • Move "close unless the merge does" into github.rs (Feature Envy). Skipped. It's Self-merge policy built from two github queries. github.rs stays a thin gh wrapper, and the policy lives in one small function in run.rs.
  • &str/String for commit SHAs and branches (Primitive Obsession). Skipped. It matches the rest of the codebase, and a SHA type belongs in its own change.
  • merge_into_develop(&scenario, "") passes an empty then. Skipped. The reviewer called it harmless, and the parameter is used by the other callers.

Spec

  • "into the default branch, thirdshift doesn't close it again or comment": a PR into the default branch that doesn't link the issue gets closed with the comment. Kept. GitHub doesn't close the issue in that case, so there's no "again". The spec's "If the issue is still open, it closes it" applies, and a test pins this down.
  • Scope beyond the issue: treating an erroring merge that landed as merged, retry_if_interrupted, and the fake's extra hooks. Kept. They serve "Ctrl-C decides the outcome by whether the PR merged" and "after it, thirdshift finishes these steps", and the tests need the hooks.
  • A terminal Ctrl-C also sends SIGINT to the running git/gh, since they share thirdshift's process group. Partly addressed. A step that fails while the Run is interrupted is retried once, and both steps are idempotent. Children don't get their own process group, because that would also stop Ctrl-C from killing a hung gh before the merge. The test harness signals only thirdshift, so the terminal case isn't tested.

Addressed from this round: the shared pr_view helper. The issue-close warning now says to close it only if it is still open, because thirdshift may not have been able to tell, and it shell-quotes the comment.

Closes #92

🤖 Generated with Claude Code

JacobStephens2 and others added 4 commits September 27, 2026 13:03
After the Self-merge, thirdshift deletes the Issue branch on origin and
closes the issue if the merge did not, with a comment naming the PR and
Base branch. Either step failing is a warning naming the manual fix; the
Run still exits 0 as merged. An interrupt after the merge no longer
matters, and a merge that reports failure but went through counts as a
merge. The fake gh gains issue close, on-merge hooks and failure
injection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Decide whether the merge closes the issue from GitHub's own rule (base
  is the default branch and the PR links the issue for closing), not from
  the issue's state a moment after the merge, which GitHub updates late.
- Count a failed gh pr merge as a merge only if the PR merged at the
  watched head, inside github::merge.
- Retry a post-merge step once if Ctrl-C killed its git or gh.
- Match the Issue branch's exact ref on origin, keeping the push error.
- Take the Base branch from the PR; tidy the test helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's before-merge `gh fake on-merge <times>` hook keeps its name; this
branch's after-merge hook becomes `gh fake after-merge`. A successful
Self-merge in the new retry loop runs the post-merge steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Share one `pr_view` helper across the four `gh pr view` calls.
- The manual issue-close hint says to run it only if the issue is still
  open, since thirdshift couldn't tell, and shell-quotes the comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit 5acf138 into main Sep 27, 2026
13 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-92 branch September 27, 2026 13:37
@JacobStephens2 JacobStephens2 mentioned this pull request Sep 27, 2026
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.

Merge run post-merge steps: delete the Issue branch, close the issue, survive failures and Ctrl-C

1 participant