This guide walks you through setting up @jno21/semantic-release-github-commit from scratch with a GitHub App for verified commits on protected branches.
- Prerequisites
- Step 1: Create a GitHub App
- Step 2: Install the App
- Step 3: Store Credentials as Secrets
- Step 4: Configure Branch Protection
- Step 5: Setup Your Release Workflow
- Step 6: Test Your Setup
- Troubleshooting
Before you begin, ensure you have:
- A GitHub repository with protected branches (e.g.,
main) - Admin access to your GitHub organization or repository
- Node.js 20+ installed locally for testing
- Basic understanding of semantic-release and GitHub Actions
For an organization:
- Go to your organization page on GitHub
- Click Settings → Developer settings → GitHub Apps
- Click New GitHub App
For a personal repository:
- Go to your GitHub profile Settings
- Click Developer settings → GitHub Apps
- Click New GitHub App
Fill in the required fields:
| Field | Value |
|---|---|
| GitHub App name | my-semantic-releaser (must be globally unique) |
| Description | Internal bot for automated releases with verified commits |
| Homepage URL | Your repository or organization URL |
| Webhook | Uncheck "Active" (not needed for this use case) |
Under Repository permissions, configure:
| Permission | Access | Why |
|---|---|---|
| Contents | Read and write | Required to push commits and create releases |
| Metadata | Read-only | Automatically selected |
| Pull requests | Read and write | (Optional) If you want to comment on PRs |
| Issues | Read and write | (Optional) If using @semantic-release/github to comment on issues |
Note: Only request the minimum permissions you need. For basic usage, only
Contents: Read and writeis required.
Under Where can this GitHub App be installed?, select:
- Only on this account (recommended for security)
Click Create GitHub App at the bottom of the page.
After creation, you'll be on the app's settings page:
- Scroll down to Private keys
- Click Generate a private key
- A
.pemfile will download automatically - Important: Store this file securely - you cannot download it again
At the top of the page, you'll see:
- App ID:
123456← Copy this number
- On the app settings page, click Install App in the left sidebar
- Click Install next to your organization or account
- Choose Only select repositories
- Select the repository where you want to use semantic-release
- Click Install
Open the downloaded .pem file in a text editor. It should look like:
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
...
-----END RSA PRIVATE KEY-----
Copy the entire contents including the BEGIN and END lines.
- Go to your repository on GitHub
- Click Settings → Secrets and variables → Actions
- Click New repository secret
Add these two secrets:
| Name | Value |
|---|---|
APP_ID |
The App ID from Step 1.7 (e.g., 123456) |
APP_PRIVATE_KEY |
The entire contents of the .pem file |
Tip: For organization-level apps used across multiple repositories, you can create organization secrets instead.
If your main branch has protection rules requiring pull requests, you need to allow the GitHub App to bypass them.
- Go to Settings → Branches
- Click Edit on your
mainbranch protection rule
Scroll down to Rules applied to everyone including administrators:
- Find the section that says Restrict who can push to matching branches
- Click Add apps
- Search for and select
my-semantic-releaser(your app name) - Click Save changes
Note: This allows the app to push commits directly to protected branches without pull requests.
You have two options for running semantic-release in GitHub Actions:
This option gives you full control over semantic-release and plugin versions.
Step 1: Install the plugin
Install the plugin as a dev dependency:
npm install --save-dev @jno21/semantic-release-github-commitOr with yarn:
yarn add --dev @jno21/semantic-release-github-commitStep 2: Configure semantic-release
Create or update your release.config.js:
module.exports = {
branches: ['main'],
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
'@semantic-release/npm',
[
'@jno21/semantic-release-github-commit',
{
files: [
'dist/**', // Build artifacts
'CHANGELOG.md', // If using @semantic-release/changelog
'package.json', // Updated version
'package-lock.json' // Updated version
],
commitMessage: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}'
}
],
'@semantic-release/github'
]
};Important: Place
@jno21/semantic-release-github-commitbefore@semantic-release/githubso the commit is created before the GitHub release.
Common configurations:
With Changelog
module.exports = {
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
[
'@semantic-release/changelog',
{
changelogFile: 'CHANGELOG.md'
}
],
'@semantic-release/npm',
[
'@jno21/semantic-release-github-commit',
{
files: ['dist/**', 'CHANGELOG.md', 'package.json', 'package-lock.json']
}
],
'@semantic-release/github'
]
};Without npm Publish
module.exports = {
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
[
'@jno21/semantic-release-github-commit',
{
files: ['dist/**', 'CHANGELOG.md']
}
],
'@semantic-release/github'
]
};Step 3: Create GitHub Actions workflow
Create .github/workflows/release.yml:
name: Release
on:
push:
branches:
- main
permissions:
contents: read
jobs:
release:
name: Release
runs-on: ubuntu-latest
# Prevent duplicate runs on release commits
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Create GitHub App Token
uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Release
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-releaseKey Points:
persist-credentials: false- Prevents conflicts with the GitHub App tokenfetch-depth: 0- Required for semantic-release to analyze commit historyif: "!contains(..., '[skip ci]')"- Prevents infinite loops from release commits- GitHub App token - Created dynamically and used instead of default
GITHUB_TOKEN
Alternatively, use the cycjimmy/semantic-release-action.
Note: With this option, you don't need to install the plugin in your package.json. The action will install it automatically via
extra_plugins.
Step 1: Configure semantic-release
Create or update your release.config.js:
module.exports = {
branches: ['main'],
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
'@semantic-release/npm',
[
'@jno21/semantic-release-github-commit',
{
files: [
'dist/**',
'CHANGELOG.md',
'package.json',
'package-lock.json'
],
commitMessage: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}'
}
],
'@semantic-release/github'
]
};Step 2: Create GitHub Actions workflow
Create .github/workflows/release.yml:
name: Release
on:
push:
branches:
- main
permissions:
contents: read
jobs:
release:
name: Release
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false
- name: Create GitHub App Token
uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v5
with:
extra_plugins: |
@jno21/semantic-release-github-commit
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}Benefits of semantic-release-action:
- No need to install semantic-release in your package.json
- Automatically caches dependencies
- Provides outputs you can use in subsequent steps (e.g.,
new_release_version)
Using the outputs:
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v5
id: semantic
with:
extra_plugins: |
@jno21/semantic-release-github-commit
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Notify on new release
if: steps.semantic.outputs.new_release_published == 'true'
run: |
echo "New release: ${{ steps.semantic.outputs.new_release_version }}"
echo "Release notes: ${{ steps.semantic.outputs.new_release_notes }}"Available outputs:
new_release_published-'true'if a new release was publishednew_release_version- Version number (e.g.,1.2.3)new_release_major_version- Major version (e.g.,1)new_release_minor_version- Minor version (e.g.,2)new_release_patch_version- Patch version (e.g.,3)new_release_git_tag- Git tag (e.g.,v1.2.3)new_release_notes- Release notes
If you stored APP_ID and APP_PRIVATE_KEY as organization secrets:
- name: Create GitHub App Token
uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}Variables can use vars context, secrets use secrets context.
Create a commit that triggers a release (using Conventional Commits):
git add .
git commit -m "feat: add new feature"
git push origin main- Go to the Actions tab in your repository
- Watch the Release workflow run
- Check the logs for each step
After the workflow completes, verify:
- New release created - Check the Releases page
- Commit is verified - The release commit should have a green "Verified" badge
- Files committed - Check that
dist/,CHANGELOG.md, etc. are updated - Tag points to correct commit - The release tag should include the plugin's commit
Cause: The GitHub App doesn't have the required permissions.
Solution:
- Go to your GitHub App settings
- Check Repository permissions → Contents is set to "Read and write"
- If you changed permissions, reinstall the app to your repository
Cause: Not using a GitHub App token, or custom identity is specified.
Solution:
- Ensure you're using
actions/create-github-app-token@v1in your workflow - Don't specify
authorName,authorEmail,committerName, orcommitterEmailin plugin config - Verify the token is passed as
GITHUB_TOKENenvironment variable
Cause: Branch protection rules prevent the app from pushing.
Solution:
- Go to Settings → Branches → Edit protection rule for
main - Under "Restrict who can push to matching branches", add your GitHub App
- Save changes
Cause: The release commit triggers another workflow run.
Solution:
- Add
[skip ci]to your commit message:{ commitMessage: 'chore(release): ${nextRelease.version} [skip ci]' }
- Add conditional to workflow:
if: "!contains(github.event.head_commit.message, '[skip ci]')"
Cause: Files don't exist or patterns are incorrect.
Solution:
- Ensure files are built before running semantic-release:
- run: npm run build - run: npx semantic-release
- Verify patterns match your file structure
- Check working directory is the repository root
Cause: Git credentials conflict or token expired.
Solution:
- Add
persist-credentials: falseto checkout step - Ensure GitHub App token is generated before semantic-release runs
- Verify
APP_PRIVATE_KEYsecret is correct
Enable debug mode to see detailed logs:
- name: Release
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
run: npx semantic-release --debug- Read VERIFIED_COMMITS.md for details on commit verification
- See TROUBLESHOOTING.md for more common issues
- Check out Configuration Options in the README