Skip to content

Release script resumes an interrupted release - #168

Merged
JacobStephens2 merged 8 commits into
mainfrom
issue-158
Sep 30, 2026
Merged

JacobStephens2 merged 8 commits into
mainfrom
issue-158

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

Running scripts/release.sh [--review] <version> again carries on from the first step not yet done. It keeps main's refusals (#157) and --review for a fresh run.

 main
   fetch_main
+  find_earlier_run          # newest PR from release-<version>, else the branch on origin
+  stop_if_tagged            # after an earlier run: tag on the merged bump → "already tagged", exit 0;
+                            #   tag elsewhere on origin → refused
+  case PR state
+    none, no branch → commit_bump (check_can_release, bump in a worktree)
+                      write_summary (--review: n → "nothing was pushed")
+                      push the branch, open_pr, wait_and_merge
+    none, branch    → write_summary (--review: n → "no PR was opened"), open_pr, wait_and_merge
+    OPEN            → wait_and_merge
+    MERGED          → use the PR's merge commit
+    CLOSED          → refused: reopen it or delete the branch
   push the tag on the merge commit

On a resume, check_can_release is skipped because the first run already passed it, and after the merge the version is no longer higher than main's. With no earlier run, an existing tag is still refused by check_can_release, in main's order and wording.

The fake gh's pr list now takes --head (exact match) and reports headRefOid and mergeCommit. The README's Releasing section gains a paragraph on resuming.

Closes #158

Evidence

  • Before: against the old script, 6 of the first 7 resume tests failed. The seventh (a tag with no bump PR) hung waiting on CI.
    After: cargo test --test release passes 35 of 35, main's refusal and review tests included. The full cargo test passes, and cargo clippy --all-targets and shellcheck scripts/release.sh are clean.
branch pushed, no PR               → PR opened on the existing head, merged, merge commit tagged
branch pushed, no PR, --review n   → "stopped at the review, so no PR was opened", origin unchanged
PR open                            → still one PR, head unchanged, merged, tagged
merged, no tag                     → the merge commit is tagged, no second merge
tag on the merged bump             → "v0.2.0 is already tagged…", exit 0, origin refs and gh state unchanged
  (lightweight and annotated)
tag elsewhere / PR open / no PR    → "refusing to release 0.2.0: v0.2.0 already exists on origin…", exit 1, origin untouched
PR closed unmerged                 → refused, origin untouched

Merge Danger

Door: two-way

Only the maintainer's release script, its test fakes and the README change. Nothing ships in the binary.

Blast Radius: release-tooling

A wrong resume decision could tag the wrong commit. The tag is only ever pushed onto the merge commit of the release-<version> PR, and an existing tag anywhere else on origin is refused.

Unaddressed findings

Standards

  • Steps return by setting globals (pr_state, url, head, merge, summary, diff), which is a Data Clump and gives the steps a hidden order. This matches how the script already shares version/tag/branch, and bash has no clean way to return several values.
  • "earlier run" and "resuming" reuse CONTEXT.md's Run and Resume. release.sh is outside the factory's domain, and those are the plain words for what it does.
  • The jq string is copied between release.sh and the fake (PR_LINE_OF_NEWEST). This follows the fake's existing WORKFLOW_RUN_LINES pattern. The fake fails loudly on any other --jq, so the two can't drift silently.
  • The resume tests repeat the push_bump / open_bump_pr / merge_bump_pr setup. Each test spells out the interrupted state it sets up. A combined helper would hide which steps the earlier run got through.
  • format!("release-{version}") is built in three test helpers. It's a small, fixed naming rule. A helper for it would save little.

Spec

  • A PR closed without merging is refused, which the spec didn't ask for. Kept, because silently opening a new PR on a branch the maintainer closed seemed worse than stopping with a hint.
  • A resumed branch is trusted without checking that it is a bump on current main. Only an earlier run creates that branch. Its PR is merged with a merge commit and CI runs on it, so a branch based on an older main still tags what CI tested.
  • Only the newest PR from the branch is read, so an older merged PR behind a newer closed one is refused. A second PR for the same version only appears if someone opens it by hand. Refusing is the safe outcome.
  • A resume doesn't refuse a local v<version> tag. The script pushes the tag from the merge commit's SHA, never from a local tag, so a local tag can't change what gets tagged. The docs now say "elsewhere on origin".
  • A pushed branch with no PR gets a new summary, and with --review is asked about again. The PR needs a summary, and the first run's summary was never saved anywhere.
  • The branch history has an empty "thirdshift: failed run" commit. Removing it would mean rewriting history that is already pushed to the open PR.

🤖 Generated with Claude Code

JacobStephens2 and others added 4 commits September 30, 2026 02:16
Run again with the same version, the release script looks for the newest
PR from release-<version> and the branch on origin: it opens the PR for
a pushed branch, waits on and merges an open PR, or tags a merged one.
A v<version> tag already on origin at the merge of the bump PR is
reported and the script exits 0; a tag anywhere else is refused, as is
a bump PR closed without merging.

The fake gh's pr list takes --head and reports headRefOid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A resumed release now tags the merge commit GitHub reports for the bump
PR rather than looking for it on main, and the helpers that end the run
or read an earlier one are named for it. The fake gh reports a PR's
mergeCommit and reads headRefOid in one place. The resume tests share
the fixture's one impl block and assert on origin and PRs only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #167's agent-written release summary. The PR body is now built
in open_pr from the bump commit's sha rather than HEAD, since the script no
longer runs inside the worktree, so version_diff takes <base> <head>. The
fake gh's pr view keeps both --jq support and headRefOid through pr_fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30T03:08:01Z, host tracewake. Uncommitted work at the time of failure is included in this commit.
@JacobStephens2
JacobStephens2 marked this pull request as draft September 30, 2026 03:08
JacobStephens2 and others added 2 commits September 30, 2026 11:57
Resume keeps main's refusals and --review: a fresh run is checked and
reviewed before its branch is pushed, a pushed branch with no PR has its
summary reviewed before the PR opens, and with no earlier run an existing
tag is refused by check_can_release as on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stop_if_tagged, as it exits 0 or refuses, and the docs say a tag
elsewhere on origin is refused. A test covers a tag while the PR is open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 marked this pull request as ready for review September 30, 2026 12:21
@JacobStephens2
JacobStephens2 merged commit bceabb7 into main Sep 30, 2026
13 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-158 branch September 30, 2026 12:36
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.

Release script resumes an interrupted release

1 participant