Skip to content

Release workflow improvements#656

Open
CassioMG wants to merge 16 commits intomainfrom
cg-release-workflow-improvements
Open

Release workflow improvements#656
CassioMG wants to merge 16 commits intomainfrom
cg-release-workflow-improvements

Conversation

@CassioMG
Copy link
Contributor

@CassioMG CassioMG commented Jan 8, 2026

What

This PR implements some improvements for the New Release workflow based on what we've discussed here, especially in regards to adding support for emergency releases.

Included in this PR:

  • The New Release workflow now opens a separate Bump version to v{version} PR so we can immediately bump the app version numbers on the main branch (sample bump version PR)
  • The New Release workflow now allow us to branch from a previous release tag (instead of main) in order to ship emergency releases without having to worry about latest changes merged to main (sample emergency release PR)
    • When you choose to branch from a previous release tag the workflow understands it's an emergency release and create the release branch as emergency-release/v{version} and open a PR with title [Emergency Release] v{version} so it's clear this is an emergency release
Screenshot 2026-01-07 at 16 36 54

If an invalid tag version is used the workflow logs will indicate it as below:
Screenshot 2026-01-07 at 16 49 47

Why

So devs have less manual work to do when issuing either a regular release or emergency release.

Known limitations

The Release Process Doc still needs to be updated with more comprehensive instructions in regards to regular releases and emergency releases. This will be done soon this week.

Checklist

PR structure

  • This PR does not mix refactoring changes with feature changes (break it down into smaller PRs if not).
  • This PR has reasonably narrow scope (break it down into smaller PRs if not).
  • This PR includes relevant before and after screenshots/videos highlighting these changes.
  • I took the time to review my own PR.

Testing

  • These changes have been tested and confirmed to work as intended on Android.
  • These changes have been tested and confirmed to work as intended on iOS.
  • These changes have been tested and confirmed to work as intended on small iOS screens.
  • These changes have been tested and confirmed to work as intended on small Android screens.
  • I have tried to break these changes while extensively testing them.
  • This PR adds tests for the new functionality or fixes.

Release

  • This is not a breaking change.
  • This PR updates existing JSDocs when applicable.
  • This PR adds JSDocs to new functionalities.
  • I've checked with the product team if we should add metrics to these changes.
  • I've shared relevant before and after screenshots/videos highlighting these changes with the design team and they've approved the changes.

@CassioMG CassioMG marked this pull request as draft January 8, 2026 00:20
@CassioMG CassioMG changed the title Release orkflow improvements Release Workflow improvements Jan 8, 2026
@CassioMG CassioMG changed the title Release Workflow improvements Release workflow improvements Jan 8, 2026
@CassioMG CassioMG marked this pull request as ready for review January 8, 2026 00:56
@CassioMG CassioMG self-assigned this Jan 8, 2026
script: |
const version = process.env.APP_VERSION;
const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/");
const title = `Bump version to v${version}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be useful for the title to be something like Bump version to v${version} MERGE NOW to remind devs that this is the PR we merge immediately?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good call, I'll add it

} catch (error) {
if (error.status === 422) {
core.warning(`Bump version pull request could not be created automatically: ${error.message}`);
const { data: existing } = await github.rest.pulls.list({
Copy link
Contributor

Choose a reason for hiding this comment

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

just curious - in the event of 422, should we be creating this PR manually or just re-running the pipeline?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it'll depend on the error message. If it indicates something intermittent is happening we could try deleting the release branch and running the pipeline again. If it indicates something that we could fix in our pipeline, then we try fixing it before running it again. If it's something out of our control or too hairy then maybe would be worth manually creating the PR while we figure things out.

But as discussed on the other thread, having the PR open is not mission critical, so as long as we have the branch there we should be able to issue and distribute the builds. :)

run: |
git push --set-upstream origin bump-version

- name: Create bump-version pull request
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to create a PR for the release branch? Your description on slack mentioned a second PR, but maybe we don't need it if we're just pushing the deploy/the tag from this branch and deleting it after

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah so the intent of the release PR here is to be mostly informational so we expose to the team what is being released on the given version, but you are right that technically speaking we don't need to open it to submit the release. Wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes sense. No need to open a PR 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

adding @aristidesstaffieri to the loop as he had the same question about opening or not a PR

I personally think it's still worth opening the PR so we have a clear way of exposing to the team what is being released, so people can easily review it specially any QA fix added and add comments/questions to it. In case we don't open the PR I think we'll lose value in terms of communication.

How do you guys feel about it? Would you have some alternative suggestion for enabling people to review/comment on release candidate changes?

Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a difference between the branch that the action uses and the emergency branch it creates?

To me the downside of this is that this flow doesn't retain the history of the work flow in git, so the PR serves as a good way for folks to see the changes but then that PR is not merged to main and we lose the historical context of the group of work(from the git log perspective).

It does feel ceremonial to open a PR we never plan to merge for the purposes of temporary visibility, especially if meaningful conversations happen in that PR but will not be easily referenced in the future if someone wants to review what happened.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a difference between the branch that the action uses and the emergency branch it creates?

humm good point, I just realized now that the "Use workflow from" dropdown also allow us to select a tag which means we could probably simplify this workflow by removing the "Branch this release from" input 🤔

But I can still see some advantage of having both "Use workflow from" and "Branch this release from" inputs as the first input would allow us to select any workflow file from any branch/tag whereas the second input will tell the workflow which branch/tag to use as reference to create the release branches, etc. If we use only 1 input for everything this means we wouldn't be able to use a more recent workflow file while still referencing an old release tag to issue an emergency release.

That being said, I'm not sure this advantage I mentioned is worth the complexity of having 2 separate inputs specially considering the second input is a text input which is not the best UX ever when comparing to having only 1 dropdown. If we need to use a more recent workflow file with an old tag we can always branch-off from the tag and update the workflow file there.

Wdyt?

especially if meaningful conversations happen in that PR but will not be easily referenced in the future if someone wants to review what happened.

Nice call. I think an approach here would be adding the Release PR reference on the GH Release Notes, so we can easily track conversations related to that specific release. => this would still require some manual work though to paste the PR reference on the GH Release notes.

I just had another idea now to automate the release footprint:

  • We have the release workflow create a third branch named release
  • The release/v{version} or emergency-release/v{version} branch is created based on the release branch
  • After QA is done and all fixes are in, we do merge the Release PR on the release branch
  • We push the tag from the release branch after the Release PR is merged
  • Every time we run the release workflow it deletes the existing release branch (if any) and creates a fresh one, which means the release branch is ephemeral so we don't need to worry about having to update or maintain it
  • BUT, since the release tag is not ephemeral this means we now have the entire release footprint including the Release PR content on git and we can easily get back to it through our release tags

How does that sound?

Copy link
Contributor Author

@CassioMG CassioMG Jan 8, 2026

Choose a reason for hiding this comment

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

👆 Adding to the above idea: considering we'd have a predictable release branch we could have another workflow dedicated to monitor when a release PR is merged to the release branch and then automatically push the release tag and trigger the iOS and Android prod build workflows. So this means once we merge the release PR it would automatically push the tag and generate the binaries we will use to submit for Apple Store and Google Play review. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FYI, I'll try the proposal above with bump-version, release and release/v1.11.24 branches to see how it goes in this release. I'll do it manually this time to use as an example, then we can work on the GH Actions improvements after we decide which git flow we'd like to automate.

Copy link
Contributor Author

@CassioMG CassioMG Jan 16, 2026

Choose a reason for hiding this comment

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

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR enhances the release workflow to support both regular releases from main and emergency releases from previous tags. It automates version bumping on the main branch via a separate PR and provides better visibility for emergency releases through distinct branch naming and PR titles.

Changes:

  • Added branch_from input parameter to allow branching from previous release tags for emergency releases
  • Introduced automatic creation of a "bump-version" PR to update version numbers on main after initiating any release
  • Implemented validation for branch/tag inputs with helpful error messages showing available tags

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 11 comments.

File Description
.github/workflows/new-release.yml Enhanced release workflow with emergency release support, version validation, base branch management for tag-based releases, and automatic bump-version PR creation
.github/actions/validate-branch-from/action.yml New composite action that validates branch_from input, normalizes tag formats, and displays available tags on validation failure

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +77 to +79
# For tags, create a base branch name from the tag
# GitHub requires PR base to be a branch, not a tag
BASE_BRANCH="base-${BRANCH_FROM}"
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The base branch name pattern "base-{tag}" could conflict with existing branches or create confusion. For example, if someone creates an emergency release from v1.15.16, it creates "base-v1.15.16". If another emergency release needs to be created from the same tag later, this branch already exists and may have diverged.

Consider using a more unique naming pattern that includes a timestamp or the new version, e.g., "emergency-base-{tag}-to-v{new_version}" or using the existing base branch only if it hasn't diverged from the tag.

Suggested change
# For tags, create a base branch name from the tag
# GitHub requires PR base to be a branch, not a tag
BASE_BRANCH="base-${BRANCH_FROM}"
# For tags, create a unique, descriptive base branch from the tag and target version
# GitHub requires PR base to be a branch, not a tag
BASE_BRANCH="emergency-base-${BRANCH_FROM}-to-v${APP_VERSION}"

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +44
echo "Available tags:"
echo "$TAGS" | while read tag; do
echo " - $tag"
done
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The 'read' command in the while loop might not work correctly if TAGS is empty. When there are no tags, the loop will still execute once with an empty value, potentially causing unnecessary output.

Consider adding a check to verify TAGS is not empty before the loop, or use a more robust iteration method like "while IFS= read -r tag".

Copilot uses AI. Check for mistakes.
Comment on lines 236 to 333
} catch (error) {
if (error.status === 422) {
core.warning(`Pull request could not be created automatically: ${error.message}`);
const { data: existing } = await github.rest.pulls.list({
owner,
repo,
head: `${owner}:${branch}`,
base: baseBranch,
state: "open"
});

if (existing.length > 0) {
await setOutputs(existing[0]);
} else {
throw error;
}
} else {
throw error;
}
}

- name: Create bump-version branch
run: |
git checkout main
git pull origin main
# Delete bump-version branch if it exists locally
git branch -D bump-version 2>/dev/null || true
# Delete bump-version branch if it exists remotely
git push origin --delete bump-version 2>/dev/null || true
git checkout -b bump-version

- name: Update app version on bump-version branch
run: |
APP_VERSION="${{ steps.normalize_version.outputs.app_version }}"
node ./scripts/set-app-version "$APP_VERSION"

- name: Commit version bump on bump-version branch
run: |
APP_VERSION="${{ steps.normalize_version.outputs.app_version }}"
git status
if [ -z "$(git status --porcelain)" ]; then
echo "No changes detected after running set-app-version."
exit 1
fi
git add .
git commit -m "chore: bump app version to v${APP_VERSION}"

- name: Push bump-version branch
run: |
git push --set-upstream origin bump-version

- name: Create bump-version pull request
uses: actions/github-script@v8
env:
APP_VERSION: ${{ steps.normalize_version.outputs.app_version }}
with:
script: |
const version = process.env.APP_VERSION;
const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/");
const title = `Bump version to v${version}`;
const body = `Bumping version on \`main\` branch to \`v${version}\`.`;

async function setOutputs(pr) {
core.notice(`Bump version pull request ready: ${pr.html_url}`);
core.setOutput("bump_version_pr_number", pr.number.toString());
core.setOutput("bump_version_pr_url", pr.html_url);
}

try {
const { data } = await github.rest.pulls.create({
owner,
repo,
head: "bump-version",
base: "main",
title,
body
});
await setOutputs(data);
} catch (error) {
if (error.status === 422) {
core.warning(`Bump version pull request could not be created automatically: ${error.message}`);
const { data: existing } = await github.rest.pulls.list({
owner,
repo,
head: `${owner}:bump-version`,
base: "main",
state: "open"
});

if (existing.length > 0) {
await setOutputs(existing[0]);
} else {
throw error;
}
} else {
throw error;
}
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The error handling for the release PR creation (lines 236-255) catches 422 errors and attempts to find existing PRs, but the error handling for the bump-version PR (lines 314-333) has the same logic. However, both use different approaches for handling the "no existing PR" case.

For the release PR, if no existing PR is found, it re-throws the error. For bump-version PR, it also re-throws. This is consistent, but if a PR already exists for the release branch with a different base (e.g., someone manually created it against 'main' instead of the base branch), the workflow will silently succeed without notifying about the base branch mismatch.

Consider adding validation that the found PR has the expected base branch.

Copilot uses AI. Check for mistakes.
Comment on lines +128 to +145
OUTPUT_DELIM="__RELEASE_DESCRIPTION__"

# For emergency releases (not from main), use simpler release notes
if [ "$BRANCH_FROM" != "main" ]; then
release_notes="Use this branch to push the emergency fix. Please replace this PR description with a description of the fix after it has been pushed."

{
echo "release_notes<<$OUTPUT_DELIM"
echo "$release_notes"
echo "$OUTPUT_DELIM"
} >> "$GITHUB_OUTPUT"

{
echo "RELEASE_NOTES<<$OUTPUT_DELIM"
echo "$release_notes"
echo "$OUTPUT_DELIM"
} >> "$GITHUB_ENV"
exit 0
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The script uses 'exit 0' to terminate early for emergency releases, which is correct. However, the OUTPUT_DELIM variable is declared twice in the function - once at line 128 for both emergency and regular releases, and would be declared again at line 169 (removed in this PR). While the current implementation works, consider clarifying that OUTPUT_DELIM is used in both paths by moving its declaration to the top of the step or adding a comment explaining why it's declared before the conditional.

Copilot uses AI. Check for mistakes.
Comment on lines +257 to +265
- name: Create bump-version branch
run: |
git checkout main
git pull origin main
# Delete bump-version branch if it exists locally
git branch -D bump-version 2>/dev/null || true
# Delete bump-version branch if it exists remotely
git push origin --delete bump-version 2>/dev/null || true
git checkout -b bump-version
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The bump-version branch is always created from main, even for emergency releases. This could lead to an inconsistent state where:

  1. The emergency release branch has version X based on an old tag
  2. The bump-version PR updates main to version X, but main has newer commits

This creates confusion about which version is which. Consider conditionally creating the bump-version PR only for regular releases from main, or adjusting the logic to handle version conflicts between emergency releases and main.

Copilot uses AI. Check for mistakes.
}

- name: Create bump-version branch
run: |
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

When checking out main at line 259, if the previous workflow steps modified the working tree state, this checkout could fail or produce unexpected results. The workflow should ensure a clean state before switching branches.

Consider adding "git reset --hard" or starting from a clean checkout of main to ensure there are no lingering changes from the release branch work.

Suggested change
run: |
run: |
# Ensure a clean working tree before switching branches
git reset --hard

Copilot uses AI. Check for mistakes.
# Emergency releases branching from previous release skip initial QA builds
# as they should be manually triggered after the emergency fixes are pushed
- name: Trigger dev builds (initial QA)
if: env.BRANCH_FROM == 'main'
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The workflow uses BRANCH_FROM environment variable for the conditional check, but in GitHub Actions, environment variables in 'if' conditions should be accessed using the expression syntax. The current syntax "env.BRANCH_FROM == 'main'" may not work as expected in all contexts.

Use the proper expression syntax: "${{ env.BRANCH_FROM == 'main' }}" or compare against the input directly: "${{ inputs.branch_from == 'main' }}"

Suggested change
if: env.BRANCH_FROM == 'main'
if: ${{ env.BRANCH_FROM == 'main' }}

Copilot uses AI. Check for mistakes.
run: |
set -eo pipefail

BRANCH_FROM="${{ env.BRANCH_FROM }}"
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The validation action successfully normalizes the tag format (adding "v" prefix if missing) and outputs it, but the original env.BRANCH_FROM still contains the unnormalized value. This is used in line 127 for the release notes condition check, which could cause the emergency release detection to fail if the user inputs "1.9.24" instead of "v1.9.24".

The workflow should use the normalized value from steps.validate_branch_from.outputs.branch_or_tag consistently throughout, or update the BRANCH_FROM environment variable with the normalized value.

Suggested change
BRANCH_FROM="${{ env.BRANCH_FROM }}"
BRANCH_FROM="${{ steps.validate_branch_from.outputs.branch_or_tag }}"

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +109
# Check if base branch already exists remotely
if git ls-remote --heads origin "$BASE_BRANCH" | grep -q "$BASE_BRANCH"; then
echo "Base branch $BASE_BRANCH already exists, checking it out"
git checkout "$BASE_BRANCH"
git pull origin "$BASE_BRANCH"
else
echo "Creating base branch $BASE_BRANCH from tag $BRANCH_FROM"
git checkout -b "$BASE_BRANCH"
git push --set-upstream origin "$BASE_BRANCH"
fi
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

If the base branch is created from a tag (lines 106-108), it's immediately pushed to origin. However, if this step fails or the workflow is re-run, the base branch might already exist remotely but not match the tag exactly (if someone pushed commits to it).

The current logic checks if the base branch exists and pulls it (lines 101-104), but doesn't verify that it still points to the same commit as the tag. This could result in creating an emergency release from a modified base branch rather than the pristine tag state.

Consider adding verification that the base branch (if it exists) points to the same commit as the tag, or force-reset it to the tag.

Copilot uses AI. Check for mistakes.
RELEASE_BRANCH="release/v${APP_VERSION}"
BASE_BRANCH="main"
else
RELEASE_BRANCH="emergency-release/v${APP_VERSION}"
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The check_release_branch job in android.yml and ios.yml workflows looks for active 'release/v*' branches to skip scheduled builds. However, this PR introduces 'emergency-release/v*' branches which won't match this pattern.

This means scheduled nightly builds will continue to run even when an emergency release branch is active, which may not be the desired behavior. Consider updating the check in android.yml and ios.yml to also look for 'emergency-release/v*' branches, or document why emergency releases shouldn't block scheduled builds.

Suggested change
RELEASE_BRANCH="emergency-release/v${APP_VERSION}"
RELEASE_BRANCH="release/v${APP_VERSION}-emergency"

Copilot uses AI. Check for mistakes.
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.

4 participants