Learn Semantic Release - Dry Run & Verification
Episode 10 of 23

Learn Semantic Release - Dry Run & Verification

Exploring the dry run as a rehearsal without side effects, how to read the generated version and changelog output, and validating release behavior between the main and staging branches before enabling an actual release.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 8 we adjusted the commit analyzer rules; in episode 9 we built the lint and build gate. Now comes the most tense moment: before a real release touches the registry and creates tags, is the computed version correct?

The answer: dry run. It's a rehearsal that runs the entire semantic-release pipeline without side effects — the version is computed, the changelog is assembled, but nothing is actually released. This episode teaches how to set up a dry run, read its output, and validate branch behavior before a release.

Main Discussion

What a Dry Run Does

A dry run follows the same flow as an actual release: takes the commits since the last tag, analyzes them with the commit analyzer, computes the version, then generates release notes. The difference: all side-effect stages are skipped:

  • No git tag is created.
  • No package is published to npm.
  • No GitHub release.
  • No CHANGELOG.md commit.

Its single purpose: give an exact picture of what would happen, so you can correct course before it's too late.

Dry Run Locally

Run it from the repository root:

Dry run in a local terminal
npx semantic-release --dry-run --no-ci

The --no-ci flag tells semantic-release this is running outside CI. Without it, the tool refuses to run because it assumes it may only be executed in a CI environment.

Example dry run output
$ npx semantic-release --dry-run --no-ci
 
[08:47:00] [semantic-release] › ℹ  Running semantic-release version 24.1.0
[08:47:00] [semantic-release] › ✔  Loaded plugin analyzeCommits
[08:47:00] [semantic-release] › ℹ  There are 3 commits on the main branch
                                   since the last release (v1.2.0).
[08:47:00] [semantic-release] › ℹ  Analyzing commit: feat(api): add retry
[08:47:00] [semantic-release] › ℹ  Analyzing commit: fix(auth): refresh token
[08:47:00] [semantic-release] › ✔  The release type for the commit is minor
[08:47:00] [semantic-release] › ℹ  The next release version is 1.3.0
[08:47:00] [semantic-release] › ✔  Generated release notes
[08:47:00] [semantic-release] › ℹ  This run was started in dry-run mode, so
                                   no release will be created.

Reading the Dry Run Output

Line in outputMeaning
There are 3 commits ... since the last releaseNumber of commits analyzed
Analyzing commit: feat(api): add retryCommit currently being evaluated
The release type for the commit is minorLevel of change from the commit
The next release version is 1.3.0Final version that would be released
Generated release notesChangelog successfully assembled
no commits since last releaseNothing worth releasing

The two most important lines are release type and next release version. Release type explains the reason (minor because there's a feat), next release version shows the exact number (up from 1.2.0 to 1.3.0). If "no commits since last release" appears, it means there's no new release — and that's not an error.

Verifying the Changelog

Below the "Generated release notes" line, semantic-release shows the draft changelog that will be used for the GitHub release. Check two things: whether only commits that genuinely deserve it appear, and whether the version heading uses the correct tag format. A noisy changelog usually indicates releaseRules that are too loose.

Dry Run in GitHub Actions

Make the dry run an automatic status check on every pull request:

Dry run job on pull request
name: Release Check
on:
  pull_request:
    branches: [main, staging]
jobs:
  dry-run:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - name: Semantic Release Dry Run
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: npx semantic-release --dry-run --no-ci

Two important details: fetch-depth: 0 makes sure the entire history is checked out so tags and commits are readable, and permissions: contents: read is sufficient because a dry run writes nothing. Reviewers can open the "Semantic Release Dry Run" step log and read the version that would come out.

Tip

Combine the dry run with the commit analyzer you customized in episode 8. Every time the releaseRules change, the dry run result on the PR immediately shows the impact before those rules are actually used. That's feedback an actual release can't give.

Validating Per-Branch Behavior

The same behavior must be verified on both branches. The same feat(api): add retry commit produces different versions depending on the branch:

$ npx semantic-release --dry-run --no-ci
[08:50:12] [semantic-release] › ℹ  The next release version is 1.3.0-rc.1
[08:50:12] [semantic-release] › ℹ  This run was started in dry-run mode

From staging comes 1.3.0-rc.1, from main comes 1.3.0. If you see an rc suffix appear on main or disappear on staging, the branch configuration in release.config.cjs is wrong and must be fixed before an actual release is enabled.

Warning

A dry run doesn't guarantee an actual release always succeeds. Things that only surface during a real release are npm publish token validation, write permissions on the registry, and GitHub release creation. So still test one actual release in a safe environment before trusting the whole pipeline.

Common Mistakes

MistakeSymptomSolution
Forgot --no-ciRefused to run outside CIAdd the flag for local dry runs
fetch-depth not setCommits or tags not readableSet fetch-depth: 0 on checkout
Misreading "no commits"Thinking the pipeline is brokenUnderstand it means no release
Only testing one branchStaging and main behavior differDry run on both branches

Conclusion

In episode 10 you:

  • Ran a full dry run without tags, publishes, or changelog commits.
  • Read the dry run output: release type for the reason and next release version for the number.
  • Set up a dry run job in GitHub Actions with fetch-depth: 0 and permissions: contents: read.
  • Validated staging and main behavior — the rc suffix must only appear on prereleases.

In episode 11 we'll discuss Branch-based Release Conditions — how to write workflow conditions so a release only happens on the right branch. See you in episode 11!

Learn Semantic Release - Dry Run & Verification | Learn Semantic Release