Skip to content

fix: hotfix release flow ships only hotfix commits and uses patch bump#12022

Merged
0xApotheosis merged 12 commits intodevelopfrom
fix/hotfix-release-flow
Mar 3, 2026
Merged

fix: hotfix release flow ships only hotfix commits and uses patch bump#12022
0xApotheosis merged 12 commits intodevelopfrom
fix/hotfix-release-flow

Conversation

@0xApotheosis
Copy link
Copy Markdown
Member

@0xApotheosis 0xApotheosis commented Feb 24, 2026

Description

The hotfix option in yarn release was shipping all commits since the last tag instead of just the hotfix commits. When a hotfix branch had any develop ancestry, getCommits() used ${latestTag}..origin/${branch} which included everything since the last release tag — not just the hotfix.

This PR fixes three issues:

  1. Commit range: Hotfix branches now use origin/main..origin/${branch} so only commits not on main are included
  2. Branch verification: After the user confirms their branch is off origin/main, a programmatic check validates the merge-base matches origin/main's SHA — catches mistakes where the branch is actually off develop
  3. Version bump: Hotfixes now use patch bump instead of minor (both at PR creation and at merge time via PR title prefix detection)

The flow:

Screenshot 2026-02-24 at 3 52 06 pm Screenshot 2026-02-24 at 3 52 19 pm Screenshot 2026-02-24 at 3 53 41 pm

Issue (if applicable)

Risk

Low risk. Only affects the scripts/release.ts CLI tool — no runtime application code changes. The release script is only run manually by developers.

What protocols, transaction types, wallets or contract interactions might be affected by this PR?

None. This is a developer tooling fix only.

Testing

Engineering

  1. Create a test branch off origin/main with a single commit
  2. Run yarn release → select Hotfix
  3. Verify the commit list shows ONLY the hotfix commit, not develop history
  4. Verify the PR title uses patch version (e.g. chore: hotfix release v1.1010.1)
  5. After PR merge, run yarn release again → verify mergeRelease() tags with patch version
  6. Run yarn release on develop → verify the hotfix commit is NOT in the regular release diff

Operations

  • 🏁 My feature is behind a flag and doesn't require operations testing (yet)

This is a developer-only CLI tool change with no user-facing impact.

Screenshots (if applicable)

N/A

Summary by CodeRabbit

  • New Features

    • Interactive selection of unreleased commits for targeted hotfix cherry-picks
    • Added confirmations and expanded progress/status messages during hotfix flow
    • Cleaner multi-line commit output for readability
  • Chores

    • Replaced draft-PR hotfix path with explicit cherry-pick, tag, merge and push sequence
    • Improved unreleased-commit detection with early exits when nothing to do
    • Hardened cherry-pick and merge flow with better error handling and automatic main reset on failure

…sion bump

The hotfix option in `yarn release` was shipping all commits since the last tag
instead of just the hotfix commits. This happened because `getCommits()` used
`${latestTag}..origin/${branch}` for hotfix branches, which includes develop
history.

- Use `origin/main..origin/${branch}` range for hotfix branches so only commits
  not on main are included
- Add programmatic verification that the hotfix branch is actually based on
  origin/main (not just the user's confirmation)
- Use patch version bump for hotfixes instead of minor
- Detect hotfix vs regular release at merge time via PR title prefix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The release script replaces the branch-clean prompt with an unreleased-commits-driven hotfix flow: discover unreleased commits between origin/main and origin/develop, prompt the user to select commits, cherry-pick selected commits onto main (with rollback on failure), tag and push main, then merge main into develop. (40 words)

Changes

Cohort / File(s) Summary
Release script
scripts/release.ts
Reworked hotfix flow: removed inquireCleanBranchOffMain and legacy draft-hotfix-PR path. Added UnreleasedCommit type and new utilities getUnreleasedCommits() and inquireSelectCommits(). Refactored getCommits(branch: string): Promise<GetCommitsReturn> (messages + total). Implemented selection-driven cherry-pick onto main (reverse order), improved error handling and rollback, tagging and pushing (with tags), private-branch reset/push, and final merge of main into develop. Adjusted messaging, confirmations, and early exits.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User as User
  participant Script as Release Script
  participant Origin as Git Remote
  participant Repo as Local Repo

  User->>Script: start hotfix flow
  Script->>Origin: fetch origin/main and origin/develop
  Origin-->>Script: return commit lists
  Script->>User: present unreleased commits (inquireSelectCommits)
  User-->>Script: selected commits
  Script->>Repo: checkout main and pull origin/main
  Script->>Repo: cherry-pick selected commits (reverse order)
  alt cherry-pick success
    Repo->>Repo: create tag on main
    Repo->>Origin: push main and tags
    Repo->>Repo: reset/push private branch to main
    Repo->>Repo: checkout develop and merge main
    Repo->>Origin: push develop
    Script->>User: complete
  else cherry-pick failure
    Repo->>Repo: reset main to pre-cherry-pick commit
    Script->>User: show error and abort
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • gomesalexandre

Poem

🐰 I sniffed the unreleased trail tonight,
Hopped the chosen commits in careful flight,
I stitched them onto main with nimble paws,
Tagged them bright and pushed without a pause,
Thump—hotfix snug, I twitch my whiskered delight.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: fixing the hotfix release flow to ship only hotfix commits and use patch version bumps instead of minor bumps.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/hotfix-release-flow

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

0xApotheosis and others added 3 commits February 24, 2026 15:47
…equiring manual branch

Hotfix flow now fetches unreleased commits (origin/main..origin/develop), presents
them as checkboxes, cherry-picks selected commits onto main, tags with patch version,
and syncs private + develop - eliminating the need to manually create a branch off main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tput correctly

simple-git's .log() collapses custom --pretty=format output into a single
entry. Switching to pify(exec) gives us raw stdout we can split by newline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@0xApotheosis 0xApotheosis marked this pull request as ready for review February 24, 2026 04:55
@0xApotheosis 0xApotheosis requested a review from a team as a code owner February 24, 2026 04:55
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 330-346: The getCommits function is misusing simple-git by passing
a raw --pretty flag which breaks simple-git's parsing and makes all[].hash
unreliable; change getCommits to call git().log with the object-style options
(e.g., include '--oneline' and '--first-parent' as keys and use a format option
with subject: '%s') and then map messages from all.map(({ subject }) => subject)
instead of hash, or for consistency switch getCommits to the same raw exec
approach used by getUnreleasedCommits; ensure you still build the same range
(latestTag..origin/branch) and return { messages, total } from getCommits.
- Around line 482-485: The loop that cherry-picks commits applies them
newest-first and doesn't handle failures; change the logic that iterates over
selected so it processes commits oldest-first (e.g., iterate over selected in
reverse order or use selected.slice().reverse()), and wrap the await
pify(exec)(`git cherry-pick ${c.hash}`) call in try/catch; on catch run a
cleanup command like pify(exec)(`git cherry-pick --abort`), log the error
(including c.hash and error.message) and exit the process with a non-zero code
to avoid leaving the repo in a conflicted state. Ensure you keep the existing
console.log message (updated to match the new order) so each attempted
cherry-pick is recorded.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 671b4fa and 1660fcf.

📒 Files selected for processing (1)
  • scripts/release.ts

Comment thread scripts/release.ts
Comment thread scripts/release.ts Outdated
0xApotheosis and others added 3 commits February 25, 2026 11:05
Use simple-git's object-style format option instead of raw --pretty flag
which broke internal parsing. Reverse cherry-pick order to oldest-first
and add error handling with abort on failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/release.ts (1)

558-558: ⚠️ Potential issue | 🟡 Minor

Missing await on doHotfixRelease() call.

The ternary awaits doRegularRelease() but not doHotfixRelease(). While Node.js won't exit with pending I/O, this inconsistency causes createRelease to return before the hotfix completes, and the implicit return type becomes a union of void and Promise<never>.

Proposed fix
 const createRelease = async () => {
-  ;(await inquireReleaseType()) === 'Regular' ? await doRegularRelease() : doHotfixRelease()
+  ;(await inquireReleaseType()) === 'Regular' ? await doRegularRelease() : await doHotfixRelease()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/release.ts` at line 558, The ternary expression that calls
inquireReleaseType() awaits the Regular branch but not the hotfix branch,
causing createRelease's return to be inconsistent; update the expression so both
branches are awaited by adding await before doHotfixRelease() (i.e., change the
ternary to await doRegularRelease() : await doHotfixRelease()), ensuring the
call sites/functions inquireReleaseType, doRegularRelease, doHotfixRelease (and
the createRelease flow) all return a consistent awaited Promise.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@scripts/release.ts`:
- Line 558: The ternary expression that calls inquireReleaseType() awaits the
Regular branch but not the hotfix branch, causing createRelease's return to be
inconsistent; update the expression so both branches are awaited by adding await
before doHotfixRelease() (i.e., change the ternary to await doRegularRelease() :
await doHotfixRelease()), ensuring the call sites/functions inquireReleaseType,
doRegularRelease, doHotfixRelease (and the createRelease flow) all return a
consistent awaited Promise.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1660fcf and 2a27db5.

📒 Files selected for processing (1)
  • scripts/release.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/release.ts (1)

558-558: ⚠️ Potential issue | 🔴 Critical

Missing await before doHotfixRelease().

The hotfix branch of the ternary doesn't await the async function. If doHotfixRelease throws before calling exit(), it results in an unhandled promise rejection that won't be caught by any error handling.

🐛 Proposed fix
 const createRelease = async () => {
-  ;(await inquireReleaseType()) === 'Regular' ? await doRegularRelease() : doHotfixRelease()
+  ;(await inquireReleaseType()) === 'Regular' ? await doRegularRelease() : await doHotfixRelease()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/release.ts` at line 558, The ternary call using inquireReleaseType()
currently awaits only doRegularRelease() but not doHotfixRelease(), causing
unhandled promise rejections; update the expression so both branches are
awaited—ensure the call using inquireReleaseType(), doRegularRelease(), and
doHotfixRelease() awaits the hotfix branch as well (i.e., add await before
doHotfixRelease()) so errors from doHotfixRelease() are properly propagated and
can be handled.
🤖 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 519-522: Wrap the merge and push around a try/catch and add
rollback: before calling git().merge(['main']) capture the current commit SHA
(e.g. const preMergeSha = await git().revparse(['HEAD'])), then try the merge
and push; on any error call git().merge(['--abort']) to stop a conflicted merge,
run git().reset(['--hard', preMergeSha]) to restore develop to the pre-merge
state, log the error via console.error, and exit/throw so the script does not
continue; ensure the push(['origin','develop']) only runs after a successful
merge. This mirrors the cherry-pick rollback pattern and uses the existing git()
helper and the merge(['main']) / push(['origin','develop']) calls as anchors.

---

Outside diff comments:
In `@scripts/release.ts`:
- Line 558: The ternary call using inquireReleaseType() currently awaits only
doRegularRelease() but not doHotfixRelease(), causing unhandled promise
rejections; update the expression so both branches are awaited—ensure the call
using inquireReleaseType(), doRegularRelease(), and doHotfixRelease() awaits the
hotfix branch as well (i.e., add await before doHotfixRelease()) so errors from
doHotfixRelease() are properly propagated and can be handled.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2a27db5 and d7aefe1.

📒 Files selected for processing (1)
  • scripts/release.ts

Comment thread scripts/release.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
scripts/release.ts (1)

516-519: ⚠️ Potential issue | 🟠 Major

Handle develop merge failures with rollback to avoid conflicted local state.

git().merge(['main']) and the following push are not protected. A conflict here can leave the repo stuck mid-merge on develop. This concern was already raised earlier and still appears unresolved in this revision.

Proposed fix
   console.log(chalk.green('Pulling develop...'))
   await git().pull()
+  const developSha = (await git().revparse(['HEAD'])).trim()
   console.log(chalk.green('Merging main back into develop...'))
-  await git().merge(['main'])
+  try {
+    await git().merge(['main'])
+  } catch (err) {
+    await git().merge(['--abort']).catch(() => undefined)
+    await git().reset(['--hard', developSha])
+    const message = err instanceof Error ? err.message : String(err)
+    exit(chalk.red(`Merge into develop failed: ${message}`))
+  }
   console.log(chalk.green('Pushing develop...'))
   await git().push(['origin', 'develop'])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/release.ts` around lines 516 - 519, Wrap the merge/push sequence in a
try/catch and guard the push so we only push when merge succeeds: call
git().merge(['main']) inside try, and on error run an explicit rollback sequence
(invoke git().raw(['merge', '--abort']) or git().merge(['--abort']) if
supported, then git().checkout(['develop']) and git().reset(['--hard',
'origin/develop']) to restore the local branch), rethrow or log the error and
exit without calling git().push(['origin', 'develop']); ensure git().merge and
git().push are the referenced symbols updated so the merge is protected and the
repo cannot remain in a conflicted mid-merge state.
🤖 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 351-354: getUnreleasedCommits() currently hardcodes the git range
to "origin/main..origin/develop" which leaks unrelated develop commits in hotfix
mode and lacks an ancestry check; modify getUnreleasedCommits to determine the
correct git range dynamically by reading the current branch (e.g., via git
rev-parse --abbrev-ref HEAD), choose the range based on whether we're in hotfix
mode (use origin/main..HEAD or origin/main..<hotfix-branch>) or normal develop
mode, and before using origin/main as the base run a git merge-base check (git
merge-base --is-ancestor origin/main HEAD) to assert the current branch is
descended from origin/main and fail with a clear error if not; apply the same
dynamic-range + ancestry-guard fix to the other similar block referenced around
lines 447-453 so both occurrences use the computed branch name, git log range,
and ancestry validation instead of the hardcoded "origin/main..origin/develop".
- Around line 359-362: The current map over stdout lines assumes a space
separator and uses spaceIdx which can be -1, causing slice(0, -1) to drop the
last char of the hash; update the mapping callback (the arrow function that uses
spaceIdx, line, hash, message) to handle the no-space case by checking if
spaceIdx === -1 and in that branch set hash = line.trim() and message = '' (or
use line.split(' ', 2) and default message to ''), otherwise keep the existing
slice logic so empty commit subjects don't corrupt the hash.

---

Duplicate comments:
In `@scripts/release.ts`:
- Around line 516-519: Wrap the merge/push sequence in a try/catch and guard the
push so we only push when merge succeeds: call git().merge(['main']) inside try,
and on error run an explicit rollback sequence (invoke git().raw(['merge',
'--abort']) or git().merge(['--abort']) if supported, then
git().checkout(['develop']) and git().reset(['--hard', 'origin/develop']) to
restore the local branch), rethrow or log the error and exit without calling
git().push(['origin', 'develop']); ensure git().merge and git().push are the
referenced symbols updated so the merge is protected and the repo cannot remain
in a conflicted mid-merge state.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d7aefe1 and 2c61561.

📒 Files selected for processing (1)
  • scripts/release.ts

Comment thread scripts/release.ts
Comment thread scripts/release.ts Outdated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
scripts/release.ts (2)

348-351: ⚠️ Potential issue | 🔴 Critical

Hotfix commit discovery is still hardcoded to origin/develop and lacks ancestry validation.

At Line 350, the range is fixed to origin/main..origin/develop, and at Line 447 hotfix mode consumes that directly. This can still surface unrelated develop commits and ship the wrong changes to main.

Proposed fix
-type UnreleasedCommit = { hash: string; message: string }
+type UnreleasedCommit = { hash: string; message: string }

-const getUnreleasedCommits = async (): Promise<UnreleasedCommit[]> => {
+const getUnreleasedCommits = async (branch: string): Promise<UnreleasedCommit[]> => {
   const result = await pify(exec)(
-    'git log --first-parent --pretty=format:"%H %s" origin/main..origin/develop',
+    `git log --first-parent --pretty=format:"%H %s" origin/main..origin/${branch}`,
   )
   const stdout = typeof result === 'string' ? result : (result as { stdout: string }).stdout

   if (!stdout.trim()) return []
@@
 const doHotfixRelease = async () => {
-  const unreleased = await getUnreleasedCommits()
+  const currentBranch = (await git().revparse(['--abbrev-ref', 'HEAD'])).trim()
+  const originMainSha = (await git().revparse(['origin/main'])).trim()
+  const mergeBaseSha = (await git().raw(['merge-base', 'origin/main', `origin/${currentBranch}`])).trim()
+  if (mergeBaseSha !== originMainSha) {
+    exit(chalk.red(`Hotfix branch must be based on origin/main. Current branch: ${currentBranch}`))
+  }
+
+  const unreleased = await getUnreleasedCommits(currentBranch)
   if (unreleased.length === 0) {
-    exit(chalk.red('No unreleased commits found between origin/main and origin/develop.'))
+    exit(chalk.red(`No unreleased commits found between origin/main and origin/${currentBranch}.`))
   }

Also applies to: 447-450

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/release.ts` around lines 348 - 351, getUnreleasedCommits currently
hardcodes the range "origin/main..origin/develop" which can include unrelated
develop commits; update getUnreleasedCommits (and any hotfix branch handling) to
compute the correct range dynamically and validate ancestry: determine the merge
base between origin/main and the target branch (e.g., with git merge-base
origin/main <branch>) and use "<merge-base>..<branch>" for git log, and
additionally verify the target branch is a descendant of origin/main (git
merge-base --is-ancestor or equivalent) before proceeding so hotfix mode only
picks commits actually based on main.

518-521: ⚠️ Potential issue | 🟠 Major

Handle main -> develop merge failures with cleanup and explicit exit.

At Line 519, a merge conflict will currently throw and leave a conflicted develop working tree. This path should mirror the cherry-pick rollback pattern to restore develop before exiting.

Proposed fix
   console.log(chalk.green('Checking out develop...'))
   await git().checkout(['develop'])
   console.log(chalk.green('Pulling develop...'))
   await git().pull()
+  const developSha = (await git().revparse(['HEAD'])).trim()
   console.log(chalk.green('Merging main back into develop...'))
-  await git().merge(['main'])
+  try {
+    await git().merge(['main'])
+  } catch (err) {
+    await git().merge(['--abort']).catch(() => undefined)
+    await git().reset(['--hard', developSha])
+    const message = err instanceof Error ? err.message : String(err)
+    exit(
+      chalk.red(
+        `Merge into develop failed: ${message}\n` +
+          `Hotfix ${nextVersion} is on main, but develop was reset to ${developSha.slice(0, 8)}. ` +
+          'Please merge main into develop manually.',
+      ),
+    )
+  }
   console.log(chalk.green('Pushing develop...'))
   await git().push(['origin', 'develop'])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/release.ts` around lines 518 - 521, Wrap the git().merge(['main'])
call in a try/catch and on error perform the same rollback steps used for
cherry-pick failures: abort the merge (git().merge(['--abort']) or equivalent),
reset/checkout develop to the previous clean state (e.g., git().reset(['--hard',
'origin/develop']) or the existing rollback routine), log the failure with
details, and then exit explicitly with process.exit(1); ensure this uses the
same helper functions/patterns as the cherry-pick rollback so that
git().merge(['main']), the cleanup, and the subsequent git().push(['origin',
'develop']) behave safely.
🤖 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 348-351: getUnreleasedCommits currently hardcodes the range
"origin/main..origin/develop" which can include unrelated develop commits;
update getUnreleasedCommits (and any hotfix branch handling) to compute the
correct range dynamically and validate ancestry: determine the merge base
between origin/main and the target branch (e.g., with git merge-base origin/main
<branch>) and use "<merge-base>..<branch>" for git log, and additionally verify
the target branch is a descendant of origin/main (git merge-base --is-ancestor
or equivalent) before proceeding so hotfix mode only picks commits actually
based on main.
- Around line 518-521: Wrap the git().merge(['main']) call in a try/catch and on
error perform the same rollback steps used for cherry-pick failures: abort the
merge (git().merge(['--abort']) or equivalent), reset/checkout develop to the
previous clean state (e.g., git().reset(['--hard', 'origin/develop']) or the
existing rollback routine), log the failure with details, and then exit
explicitly with process.exit(1); ensure this uses the same helper
functions/patterns as the cherry-pick rollback so that git().merge(['main']),
the cleanup, and the subsequent git().push(['origin', 'develop']) behave safely.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2c61561 and e7ceb1f.

📒 Files selected for processing (1)
  • scripts/release.ts

Guard against empty commit subjects in getUnreleasedCommits where
spaceIdx === -1 would corrupt the hash via slice(0, -1).

Add try/catch around the merge-main-into-develop step in both hotfix
and regular release flows so a conflict doesn't leave develop in a
broken state with no recovery guidance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@NeOMakinG NeOMakinG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — PR #12022

Verdict: ✅ APPROVE (with one required fix)

Summary: Major improvement to the hotfix release flow. Replaces the fragile "branch off main → force push → draft PR" approach with a clean cherry-pick workflow: show unreleased commits → user selects → cherry-pick onto main → tag with patch version → sync private + develop. Proper rollback on failure. gomes-bot already did a thorough deep review — I concur with the approval.

Architecture Assessment

The new flow is fundamentally better:

  • Old: User manually branches off main, self-reports ancestry, force-pushes, creates draft PR. Wrong commit range (latestTag..origin/branch) pulled develop history.
  • New: Script fetches unreleased commits (origin/main..origin/develop), presents interactive checkbox selection, cherry-picks oldest-first onto main, tags with patch version, syncs private + develop. User never leaves the script.

What's Good

  • Cherry-pick with rollback: On failure → cherry-pick --abortreset --hard mainSha → clean exit with message. Well-implemented.
  • Develop merge with rollback: Same pattern — merge --abortreset --hard developSha → clean exit. Applied in both doHotfixRelease and mergeRelease.
  • Defensive parsing: spaceIdx === -1 guard prevents hash corruption from empty commit subjects.
  • Patch bump instead of minor — correct per semver for hotfixes.
  • getUnreleasedCommits() uses origin/main..origin/develop — this is correct for the new flow (cherry-pick model, not branch model). CodeRabbit kept flagging it but it's by design.

⚠️ Required Fix: Missing await on doHotfixRelease()

Line 524:

;(await inquireReleaseType()) === 'Regular' ? await doRegularRelease() : doHotfixRelease()

doHotfixRelease() is NOT awaited. With the old flow this was mostly harmless (just created a draft PR), but now doHotfixRelease does cherry-picks, tags, pushes, and develop merges — all critical async operations. Without await, the script could exit mid-operation or swallow errors as unhandled rejections.

Fix: Add await before doHotfixRelease().

Risk Assessment

Low risk — Developer-only CLI tool, no runtime code changes. The missing await is the only real concern and is a one-word fix.

Browser Test

N/A — CLI tooling change, no UI surface.

Without await, the script could exit mid-operation or swallow errors
as unhandled rejections during cherry-picks, tags, pushes, and
develop merges.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@0xApotheosis 0xApotheosis merged commit 2ca41a3 into develop Mar 3, 2026
4 checks passed
@0xApotheosis 0xApotheosis deleted the fix/hotfix-release-flow branch March 3, 2026 04:04
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.

2 participants