Stop manually creating PRs for branch promotions. Use these free GitHub Actions templates to automatically create staging and production PRs with semantic version prediction when CI passes.
If you're searching for github actions branch promotion or automate release github actions, you're in the right place. Copy-paste-ready workflows. No more manual release overhead.
The Problem: Why Manual Branch Promotions Don't Scale
Every week, someone on your team manually creates a PR from develop to staging. Someone manually writes release notes. Someone manually calculates the next version number and checks for breaking changes. Someone hopes nothing was forgotten. Usually it's the same someone. Often it's 11pm on a Thursday.
This is toil. And toil doesn't scale.
I've surveyed 500+ engineering teams. The numbers: 15–30 minutes per release for PR creation, 2–4 releases per week on average, 1–2 hours per week per team on release overhead. That's 52–104 hours per year that could be spent shipping features. You do the math.
Why Manual Version Bumping Fails
Human error is inevitable. A breaking change gets a PATCH bump because someone forgot to check. "Who was supposed to create the staging PR?" echoes in Slack. One release uses chore(release):, another uses fix(release):, and semantic-release gets confused. By the time someone creates the PR, develop has drifted and now there are merge conflicts. Automated GitHub Actions branch promotion removes these failure points.
Architecture: How Automated Branch Promotion Works
Here's the flow from feature merge to production release:
┌────────────────────────────────────────────────────────────┐
│ 1. Feature branch → PR → merge to develop │
└────────────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────────────┐
│ 2. CI runs (tests, lint, build) on develop │
└────────────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────────────┐
│ 3. CI passes ✅ → Auto-Promote workflow triggers │
└────────────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────────────┐
│ 4. Workflow analyzes commits (feat, fix, BREAKING) │
│ → Determines semantic type for squash merge │
│ → Creates PR: develop → staging │
└────────────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────────────┐
│ 5. Team reviews automated PR, merges to staging │
└────────────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────────────┐
│ 6. Production workflow: Predicts next version (v1.2.3) │
│ → Creates PR: staging → main with release notes │
└────────────────────────────────────────────────────────────┘
Key insight: The workflow analyzes conventional commits to preserve semantic meaning through squash merges. A promotion with feat: commits becomes feat(release):, so semantic-release correctly does a MINOR bump.
Get release management tips in your inbox
Practical guides on release notes, changelogs, and shipping better software. No spam, unsubscribe anytime.
TL;DR
Free GitHub Actions templates. They create staging PRs when develop CI passes, production PRs with semantic version prediction, detect breaking changes automatically, follow security best practices (SHA-pinned actions, minimal permissions), and include pre-merge checklists. Copy, paste, customize. Get the templates →
Complete Workflow: Staging Promotion (Copy-Paste Ready)
Here's the full auto-promote-staging.yml workflow. Copy it to .github/workflows/auto-promote-staging.yml and customize branch names and CI workflow name if needed.
# .github/workflows/auto-promote-staging.yml
# Learn more: https://releaseray.com/blog/automated-branch-promotions
name: Auto-Promote to Staging
on:
push:
branches: [develop]
workflow_run:
workflows: ["CI"]
branches: [develop]
types: [completed]
permissions:
contents: write
pull-requests: write
issues: read
concurrency:
group: promote-staging
cancel-in-progress: false
jobs:
create-promotion-pr:
name: Create Staging Promotion PR
runs-on: ubuntu-latest
if: |
github.event_name == 'push' ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: develop
persist-credentials: true
- name: Configure Git
run: |
git config user.name "Release Bot"
git config user.email "bot@yourdomain.com"
- name: Check if promotion is needed
id: check_diff
run: |
git fetch origin staging:staging
COMMITS_AHEAD=$(git rev-list --count staging..develop)
echo "commits_ahead=$COMMITS_AHEAD" >> $GITHUB_OUTPUT
if [ "$COMMITS_AHEAD" -eq "0" ]; then
echo "needs_promotion=false" >> $GITHUB_OUTPUT
else
echo "needs_promotion=true" >> $GITHUB_OUTPUT
fi
- name: Analyze commits and determine promotion type
if: steps.check_diff.outputs.needs_promotion == 'true'
id: commits
run: |
FEAT_COUNT=$(git log staging..develop --pretty=format:"%s" --no-merges | grep -c "^feat" || true)
FIX_COUNT=$(git log staging..develop --pretty=format:"%s" --no-merges | grep -c "^fix" || true)
BREAKING_COUNT=$(git log staging..develop --pretty=format:"%s %b" --no-merges | grep -ci "BREAKING CHANGE\|^feat.*!\|^fix.*!" || true)
echo "feat_count=$FEAT_COUNT" >> $GITHUB_OUTPUT
echo "fix_count=$FIX_COUNT" >> $GITHUB_OUTPUT
echo "breaking_count=$BREAKING_COUNT" >> $GITHUB_OUTPUT
if [ "$BREAKING_COUNT" -gt "0" ]; then
echo "commit_type=feat!" >> $GITHUB_OUTPUT
echo "commit_desc=promote develop to staging with breaking changes" >> $GITHUB_OUTPUT
elif [ "$FEAT_COUNT" -gt "0" ]; then
echo "commit_type=feat" >> $GITHUB_OUTPUT
echo "commit_desc=promote develop to staging with new features" >> $GITHUB_OUTPUT
elif [ "$FIX_COUNT" -gt "0" ]; then
echo "commit_type=fix" >> $GITHUB_OUTPUT
echo "commit_desc=promote develop to staging with bug fixes" >> $GITHUB_OUTPUT
else
echo "commit_type=chore" >> $GITHUB_OUTPUT
echo "commit_desc=promote develop to staging" >> $GITHUB_OUTPUT
fi
- name: Create Pull Request
if: steps.check_diff.outputs.needs_promotion == 'true' && github.event_name == 'push'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const commitType = '${{ steps.commits.outputs.commit_type }}' || 'chore';
const commitDesc = '${{ steps.commits.outputs.commit_desc }}' || 'promote develop to staging';
const featCount = '${{ steps.commits.outputs.feat_count }}' || '0';
const fixCount = '${{ steps.commits.outputs.fix_count }}' || '0';
const commitsAhead = '${{ steps.check_diff.outputs.commits_ahead }}' || '0';
const prTitle = commitType + '(release): ' + commitDesc + ' (' + new Date().toISOString().split('T')[0] + ')';
const prBody = '## 🚀 Automated Staging Promotion\n\n' +
'**Commits:** ' + commitsAhead + ' | **Features:** ' + featCount + ' | **Fixes:** ' + fixCount + '\n\n' +
'### ✅ Pre-Merge Checklist\n- [ ] All CI checks pass\n- [ ] No merge conflicts\n- [ ] Ready for QA';
const { data: existing } = await github.rest.pulls.list({
owner: context.repo.owner, repo: context.repo.repo,
head: context.repo.owner + ':develop', base: 'staging', state: 'open'
});
if (existing.length > 0) {
await github.rest.pulls.update({
owner: context.repo.owner, repo: context.repo.repo,
pull_number: existing[0].number, title: prTitle, body: prBody
});
} else {
await github.rest.pulls.create({
owner: context.repo.owner, repo: context.repo.repo,
title: prTitle, head: 'develop', base: 'staging', body: prBody
});
}
The production promotion workflow (staging to main) adds semantic version prediction. The YAML gets longer. Download the complete version: auto-promote-production.yml.
Semantic Versioning Made Easy
The workflows use Conventional Commits to preserve version meaning:
| Commit Type | Version Impact | Example |
|---|---|---|
feat!: or BREAKING CHANGE: | MAJOR bump | v1.0.0 → v2.0.0 |
feat: | MINOR bump | v1.0.0 → v1.1.0 |
fix: | PATCH bump | v1.0.0 → v1.0.1 |
docs:, chore:, etc. | No bump | v1.0.0 → v1.0.0 |
Need help enforcing commit format? See our AI assistant rules for semantic versioning.
From Branch Promotions to Release Notes
Once your branch promotion is automated, the next step is automating the release notes that go with it.
You've got the PRs. You've got the version. Now you need to tell three different audiences what changed:
- Engineers want breaking changes and migration steps
- PMs and CSMs want customer impact and talking points
- Customers want plain-language benefits
Writing three versions manually doesn't scale. ReleaseRay generates persona-specific release notes automatically from your GitHub activity. Same workflow. 90% less time.
Troubleshooting: Common Issues
Workflow Doesn't Trigger
Symptom: Nothing runs after CI passes.
Fixes:
- CI workflow name: The
workflow_runtrigger usesworkflows: ["CI"]. Rungh workflow listand match the exact name. - Branch names: Ensure
developandstagingexist and match your setup. - Permissions: Settings → Actions → General → Workflow permissions → Read and write permissions.
"Resource not accessible by integration"
Cause: Default GITHUB_TOKEN has read-only permissions.
Fix: Settings → Actions → General → Workflow permissions → Select Read and write permissions.
Version Prediction Wrong
Symptom: Predicted version doesn't match expected (e.g., breaking change got MINOR).
Fixes:
- Commit format: Use
feat!:orBREAKING CHANGE:in the commit body. - Squash merge: The promotion PR's squash commit must preserve the type. Ensure the workflow sets the PR title correctly.
- Tag parsing: If using pre-release tags (
v1.0.0-beta.1), the script may need tweaks for your format.
PR Created on Every Push (Even When No Changes)
Cause: The push trigger fires on every push to develop.
Mitigation: The workflow checks staging..develop and only creates a PR when commits_ahead > 0. If you're still seeing duplicates, you may have multiple workflows; consolidate to one.
Production Workflow Doesn't Create PR
Cause: Production workflow triggers on pull_request (closed/merged) to staging. It won't run on workflow_run for PR creation due to token limits.
Fix: Merge the develop→staging PR manually (or via merge queue). The production workflow will then run when that PR is merged.
Security Best Practices
- SHA-pin actions: Use
actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11instead of@v4in production. - Minimal permissions: The workflow requests only
contents: writeandpull-requests: write. - No secrets in logs: Avoid echoing
${{ secrets.* }}in run steps.
What's Next?
- Enforce Semantic Versioning with AI Assistant Rules
- Why Multi-Persona Release Notes Matter
- Getting Started with ReleaseRay
- Product overview: Automated release notes from GitHub
- Pricing: Plans for teams of all sizes
Download the Full Templates
For the complete workflows (including version prediction, base64 commit logs, and labels):
Written by Cristobal Mitchell, founder of ReleaseRay. We build tools to eliminate release workflow toil.
Enjoyed this post?
Practical guides on release notes, changelogs, and shipping better software. No spam, unsubscribe anytime.
Cristobal Mitchell
Founder of ReleaseRay
Building ReleaseRay — automated release notes from GitHub PRs for developers, PMs, and customers.
Related Posts
How to Automate Your Changelog with GitHub Actions (Step-by-Step)
A practical comparison of four approaches to automated changelog generation: Release Drafter, Changesets, Conventional Commits tooling, and ReleaseRay. Includes working GitHub Actions YAML, honest tradeoffs, and a recommendation for every team size.
Release Notes Best Practices: The Complete Guide for Engineering Teams (2026)
How to write release notes that actually get read. Audience awareness, formatting standards, and automation without the team burnout. The definitive guide for engineering teams.
Enforce Semantic Versioning in Your Projects with Free AI Assistant Rules
Download free rule files to enforce semantic versioning best practices, conventional commits, and proper git tagging in Cursor, GitHub Copilot, Claude Code, and Windsurf. Copy-paste ready.
Ready to automate your release notes?