fix: release script squash-merge compat and backmerge automation#12162
fix: release script squash-merge compat and backmerge automation#12162gomesalexandre wants to merge 6 commits intodevelopfrom
Conversation
merged_untagged case was creating a private sync PR without checking if one already existed, unlike tagged_private_stale which had the guard. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
tagged_private_stale case was creating a private sync PR even when private was already content-identical to main (SHA mismatch due to propagation delay after a sync PR merges). Added a content diff check to bail early in that case. Same guard applied to the hotfix path. The merged_untagged case also gets the open-PR guard for belt-and-suspenders. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
📝 WalkthroughWalkthroughRelease script now uses commit- and content-diff checks to decide prerelease state, conditionally creates or reuses private sync PRs, creates and auto-merges backmerge PRs from main→develop when needed, and updates logging across regular and hotfix flows. Changes
Sequence Diagram(s)sequenceDiagram
participant Release as Release Script
participant Git as Git (origin/main, origin/release, origin/private, develop)
participant GH as GitHub (PRs, Tags)
Release->>Git: fetch SHAs, commits and content diffs
alt release has no unique commits OR content identical to main
Release->>GH: mark prereleaseMerged / log and stop
else
Release->>GH: create release tag
Release->>GH: query open PRs main→private
alt open private PR exists
GH-->>Release: return PR info
Release->>GH: log "reusing existing private PR"
else
Release->>Git: compute private vs main content diff
alt private behind main
Release->>GH: create private-to-main PR
GH-->>Release: return PR URL
else
Release->>GH: log "private in sync, skip PR"
end
end
Release->>Git: compute commits on main since develop
alt commits exist AND no backmerge PR exists
Release->>GH: create backmerge PR (main→develop)
Release->>GH: enable auto-merge (merge-commit)
GH-->>Release: return PR + auto-merge status
else
Release->>GH: log "no backmerge needed"
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
scripts/release.ts (1)
805-809: Content guard applied consistently to Hotfix path — butmerged_untaggedguard is missing.The content diff check mirrors the Regular release path correctly. However, the Hotfix
merged_untaggedcase (lines 781-798) still creates PRs unconditionally without checking for existing ones, unlike the guarded Regular release path at lines 613-630.Per the PR description intent to "add an open-PR guard to
merged_untagged", consider applying the same pattern to the Hotfix path for full consistency:Proposed fix for Hotfix merged_untagged guard
case 'merged_untagged': { console.log(chalk.green(`Hotfix merged to main. Tagging ${nextVersion}...`)) await git().checkout(['main']) await git().pull() await git().tag(['-a', nextVersion, '-m', nextVersion]) console.log(chalk.green('Pushing tag...')) await git().push(['origin', '--tags']) console.log(chalk.green(`Tagged ${nextVersion}.`)) - console.log(chalk.green('Creating PR to sync private to main...')) - const privatePrUrl = await createPr({ - base: 'private', - head: 'main', - title: `chore: sync private to ${nextVersion}`, - body: `Sync private branch to main after hotfix ${nextVersion}.`, - }) - console.log(chalk.green(`Private sync PR created: ${privatePrUrl}`)) + const existingPrivatePrAfterTag = await findOpenPr('main', 'private') + if (existingPrivatePrAfterTag) { + console.log( + chalk.yellow( + `Private sync PR already open: #${existingPrivatePrAfterTag.number}. Merge it on GitHub.`, + ), + ) + } else { + console.log(chalk.green('Creating PR to sync private to main...')) + const privatePrUrl = await createPr({ + base: 'private', + head: 'main', + title: `chore: sync private to ${nextVersion}`, + body: `Sync private branch to main after hotfix ${nextVersion}.`, + }) + console.log(chalk.green(`Private sync PR created: ${privatePrUrl}`)) + } - console.log(chalk.green('Creating backmerge PR (main -> develop)...')) - const backmergeUrl = await createPr({ - base: 'develop', - head: 'main', - title: `chore: backmerge ${nextVersion} into develop`, - body: `Backmerge main into develop after hotfix ${nextVersion} to sync cherry-picked commits.`, - }) - console.log(chalk.green(`Backmerge PR created: ${backmergeUrl}`)) - console.log(chalk.green('Merge both PRs on GitHub to complete the hotfix.')) + const existingBackmergePrAfterTag = await findOpenPr('main', 'develop') + if (existingBackmergePrAfterTag) { + console.log( + chalk.yellow( + `Backmerge PR already open: #${existingBackmergePrAfterTag.number}. Merge it on GitHub.`, + ), + ) + } else { + console.log(chalk.green('Creating backmerge PR (main -> develop)...')) + const backmergeUrl = await createPr({ + base: 'develop', + head: 'main', + title: `chore: backmerge ${nextVersion} into develop`, + body: `Backmerge main into develop after hotfix ${nextVersion} to sync cherry-picked commits.`, + }) + console.log(chalk.green(`Backmerge PR created: ${backmergeUrl}`)) + } + console.log(chalk.green('Merge PRs on GitHub to complete the hotfix.')) break }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/release.ts` around lines 805 - 809, The Hotfix `merged_untagged` path is missing the open-PR guard present in the Regular release flow; modify the Hotfix branch handling that uses `privateDiffHotfix`/`merged_untagged` so it first queries for an existing open PR (reuse the same check used in the Regular release path) and only calls the PR creation routine (e.g., the existing `createPullRequest`/createPR call) when no open PR is found; if an open PR exists, skip creating a new one and log or exit the same way the Regular path does.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@scripts/release.ts`:
- Around line 805-809: The Hotfix `merged_untagged` path is missing the open-PR
guard present in the Regular release flow; modify the Hotfix branch handling
that uses `privateDiffHotfix`/`merged_untagged` so it first queries for an
existing open PR (reuse the same check used in the Regular release path) and
only calls the PR creation routine (e.g., the existing
`createPullRequest`/createPR call) when no open PR is found; if an open PR
exists, skip creating a new one and log or exit the same way the Regular path
does.
…ate_private_sync_pr_v2
…rge auto-merge - idle case: use git diff content check instead of SHA equality for prereleaseMerged - squash merges diverge SHAs even when content matches - tagged_private_stale (regular + hotfix): set auto-merge with merge commit strategy on backmerge PR so it lands without manual intervention Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/release.ts`:
- Around line 639-645: The early break when privateDiff is empty stops further
backmerge logic; remove the break (or replace it with no-op) so after logging
"Private is already in sync with main content-wise. Nothing to do." the script
continues to the subsequent backmerge checks and PR creation. Update both
occurrences around the privateDiff check (the const privateDiff = await
git().diff([...]) blocks and the similar block at the later lines) to log but
not exit the loop so the backmerge automation (main -> develop PR creation) can
still run.
- Around line 518-520: The prereleaseMerged boolean currently only checks
SHA/content mismatch (releaseMatchesMain) and can be true when origin/release is
behind origin/main; update the logic so prereleaseMerged is only true when
origin/release actually contains commits not present on origin/main (i.e.,
release is ahead), not merely when SHAs/content differ. Replace the current
check that sets prereleaseMerged (which references releaseSha, mainSha,
releaseMatchesMain and git().diff) with a check that runs a git rev-list/log
between origin/release and origin/main (e.g., list commits in origin/release
^origin/main) and considers prereleaseMerged true only if that list is
non-empty.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
…eak in tagged_private_stale - idle: replace SHA/content-diff prereleaseMerged with commit-ahead check (origin/main..origin/release) - prevents false positive when release is *behind* main (e.g. post-hotfix), which would have routed into release PR path with 0 commits - tagged_private_stale (regular + hotfix): remove early break when private is content-synced - script must still evaluate backmerge PR creation even when private sync is a no-op Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/release.ts`:
- Around line 669-687: The backmerge branch creates auto-merge only when a new
PR is created, so reruns where findOpenPr('main', 'develop') returns an
existingBackmerge never enable auto-merge; update the logic so after detecting
an existingBackmerge you extract its identifier/URL (from the existingBackmerge
object returned by findOpenPr) and run the same pify(exec)(`gh pr merge --auto
--merge ${prIdentifierOrUrl}`) call to enable auto-merge, and apply the
identical change to the hotfix backmerge block (the other block that uses
createPr/getCommitMessages and gh pr merge).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| const existingBackmerge = await findOpenPr('main', 'develop') | ||
| if (!existingBackmerge) { | ||
| const mainDevelopCommits = await getCommitMessages('origin/develop..origin/main') | ||
| if (mainDevelopCommits.length > 0) { | ||
| console.log(chalk.green('Creating backmerge PR (main -> develop)...')) | ||
| const backmergeUrl = await createPr({ | ||
| base: 'develop', | ||
| head: 'main', | ||
| title: `chore: backmerge ${nextVersion} into develop`, | ||
| body: `Backmerge main into develop after release ${nextVersion}.`, | ||
| }) | ||
| console.log(chalk.green(`Backmerge PR created: ${backmergeUrl}`)) | ||
| console.log(chalk.green('Setting auto-merge with merge commit strategy...')) | ||
| await pify(exec)(`gh pr merge --auto --merge ${backmergeUrl}`) | ||
| console.log( | ||
| chalk.green('Auto-merge set. Backmerge will merge automatically when CI passes.'), | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
Auto-merge is skipped when backmerge PR already exists.
Right now, gh pr merge --auto --merge ... runs only in the “new PR created” branch. On reruns where a backmerge PR is already open, auto-merge is never set, so automation can stall.
🔧 Minimal fix (apply in both regular + hotfix backmerge blocks)
- const existingBackmerge = await findOpenPr('main', 'develop')
- if (!existingBackmerge) {
+ const existingBackmerge = await findOpenPr('main', 'develop')
+ if (existingBackmerge) {
+ console.log(chalk.yellow(`Backmerge PR already open: #${existingBackmerge.number}.`))
+ console.log(chalk.green('Setting auto-merge with merge commit strategy...'))
+ await pify(exec)(`gh pr merge --auto --merge ${existingBackmerge.number}`)
+ console.log(
+ chalk.green('Auto-merge set. Backmerge will merge automatically when CI passes.'),
+ )
+ } else {
const mainDevelopCommits = await getCommitMessages('origin/develop..origin/main')
if (mainDevelopCommits.length > 0) {
console.log(chalk.green('Creating backmerge PR (main -> develop)...'))
const backmergeUrl = await createPr({
base: 'develop',
head: 'main',
title: `chore: backmerge ${nextVersion} into develop`,
body: `Backmerge main into develop after release ${nextVersion}.`,
})
console.log(chalk.green(`Backmerge PR created: ${backmergeUrl}`))
console.log(chalk.green('Setting auto-merge with merge commit strategy...'))
await pify(exec)(`gh pr merge --auto --merge ${backmergeUrl}`)
console.log(
chalk.green('Auto-merge set. Backmerge will merge automatically when CI passes.'),
)
}
}Also applies to: 860-877
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/release.ts` around lines 669 - 687, The backmerge branch creates
auto-merge only when a new PR is created, so reruns where findOpenPr('main',
'develop') returns an existingBackmerge never enable auto-merge; update the
logic so after detecting an existingBackmerge you extract its identifier/URL
(from the existingBackmerge object returned by findOpenPr) and run the same
pify(exec)(`gh pr merge --auto --merge ${prIdentifierOrUrl}`) call to enable
auto-merge, and apply the identical change to the hotfix backmerge block (the
other block that uses createPr/getCommitMessages and gh pr merge).
There was a problem hiding this comment.
♻️ Duplicate comments (1)
scripts/release.ts (1)
671-689:⚠️ Potential issue | 🟠 MajorAuto-merge is still not enabled when a backmerge PR already exists.
When
existingBackmergeis found, the script currently does nothing. On reruns, this can leave backmerge PRs open without auto-merge configured and stall automation.🔧 Minimal fix (apply in both regular + hotfix backmerge blocks)
const existingBackmerge = await findOpenPr('main', 'develop') -if (!existingBackmerge) { +if (existingBackmerge) { + console.log(chalk.yellow(`Backmerge PR already open: #${existingBackmerge.number}.`)) + console.log(chalk.green('Setting auto-merge with merge commit strategy...')) + await pify(exec)(`gh pr merge --auto --merge ${existingBackmerge.number}`) + console.log(chalk.green('Auto-merge set. Backmerge will merge automatically when CI passes.')) +} else { const mainDevelopCommits = await getCommitMessages('origin/develop..origin/main') if (mainDevelopCommits.length > 0) { console.log(chalk.green('Creating backmerge PR (main -> develop)...')) const backmergeUrl = await createPr({ base: 'develop', head: 'main', title: `chore: backmerge ${nextVersion} into develop`, body: `Backmerge main into develop after release ${nextVersion}.`, }) console.log(chalk.green(`Backmerge PR created: ${backmergeUrl}`)) console.log(chalk.green('Setting auto-merge with merge commit strategy...')) await pify(exec)(`gh pr merge --auto --merge ${backmergeUrl}`) console.log(chalk.green('Auto-merge set. Backmerge will merge automatically when CI passes.')) } }Also applies to: 864-882
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/release.ts` around lines 671 - 689, The code currently skips configuring auto-merge when findOpenPr returns an existingBackmerge, leaving PRs open without auto-merge; update the logic in the backmerge block around existingBackmerge (and mirror the same change in the hotfix backmerge block) so that if existingBackmerge is truthy you call the same auto-merge command used after createPr (i.e., invoke pify(exec) with the gh pr merge --auto --merge argument using existingBackmerge's URL or number), logging the same progress messages as when a PR is newly created so reruns will enable auto-merge on already-open backmerge PRs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@scripts/release.ts`:
- Around line 671-689: The code currently skips configuring auto-merge when
findOpenPr returns an existingBackmerge, leaving PRs open without auto-merge;
update the logic in the backmerge block around existingBackmerge (and mirror the
same change in the hotfix backmerge block) so that if existingBackmerge is
truthy you call the same auto-merge command used after createPr (i.e., invoke
pify(exec) with the gh pr merge --auto --merge argument using
existingBackmerge's URL or number), logging the same progress messages as when a
PR is newly created so reruns will enable auto-merge on already-open backmerge
PRs.
Description
Three fixes to the release state machine in `scripts/release.ts`, all rooted in the same underlying problem: the script was designed (in #12110) assuming rebase merging (same SHAs post-merge), but the repo uses squash merging.
1. `idle` case - content diff instead of SHA equality for `prereleaseMerged`
Before:
```ts
const prereleaseMerged = releaseSha !== mainSha
```
After squash-merging a release PR, `releaseSha !== mainSha` is always true (squash creates a new commit), so the script always thought there was a pending release to cut even right after a fresh one. Fixed by comparing actual file content:
```ts
const releaseMatchesMain = !(await git().diff(['origin/main', 'origin/release']))
const prereleaseMerged = releaseSha !== mainSha && !releaseMatchesMain
```
2. `tagged_private_stale` (regular release) - add backmerge PR with auto-merge
After main gets tagged and private is synced, main has diverged from develop (the squash-merged release commit isn't in develop's history). The script was leaving this cleanup to be done manually.
Now creates a `main -> develop` backmerge PR automatically and sets auto-merge with merge commit strategy:
```ts
await pify(exec)(`gh pr merge --auto --merge ${backmergeUrl}`)
```
Merge commit strategy is intentional - preserves main's commit in develop's history so the merge base is correct for future releases.
3. `tagged_private_stale` (hotfix) - same auto-merge treatment
The hotfix path already created the backmerge PR but wasn't setting auto-merge. Now consistent with the regular release path.
Issue (if applicable)
closes #
Risk
Low - release script only, no production code touched. Worst case is a script error requiring manual intervention (same as before).
Testing
Engineering
Run `pnpm release` after a release has been tagged and private is stale:
For the `idle` fix: run `pnpm release` right after squash-merging a release - should land in the "fresh start" sub-state (create prerelease PR) instead of "prerelease merged".
Operations
Summary by CodeRabbit