Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

5.2 Software Versioning

What is Versioning?

Versioning is the process of assigning unique version numbers to distinct states of software projects, allowing developers and users to track progress, manage changes, and ensure compatibility between different components. It plays a critical role in software development and release management, offering a structured way to reflect the history, stability, and compatibility of software products over time. Through versioning, teams can effectively communicate the impact of changes, manage dependencies, and facilitate the adoption of new features while maintaining the integrity of existing systems.

What is Semantic Versioning?

Semantic Versioning, often abbreviated as SemVer, is a versioning scheme that aims to convey meaning about the underlying changes in a release through the version number itself. Defined by Semantic Versioning 2.0.0, it adopts a structured format of MAJOR.MINOR.PATCH to differentiate between the types of changes made to a project:

Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

The principles of Semantic Versioning help ensure a consistent, predictable approach to versioning that is directly tied to the significance of the changes made. It allows developers and consumers of software to make informed decisions about upgrading and integrating with other systems. By adhering to SemVer, projects can communicate the nature of changes efficiently, reduce the potential for conflicts, and facilitate easier dependency management in the complex ecosystem of software development.

Automate Semantic Versioning with Python Semantic Release

Using python-semantic-release to automate semantic versioning in projects managed with uv on GitHub involves setting up python-semantic-release in your project, configuring it to work with uv, and automating the release process through GitHub Actions.

Setup Your Python Project with uv

Let’s start by creating a new Python project using uv and navigating to its root directory.

uv init my-package-with-semver
Initialized project `my-package-with-semver` at `/home/runner/work/msdp-book/home/ch52/my-package-with-semver`

Navigate to the root folder of the project.

cd my-package-with-semver

Install Python Semantic Release

Since this dependency is only needed for development and release management, it’s best to add it as a development dependency. This way, it won’t be included in the production environment or when users install your package.

uv add --dev python-semantic-release
warning: `VIRTUAL_ENV=/home/runner/work/msdp-book/msdp-book/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead
Using CPython 3.13.13
Creating virtual environment at: .venv
Resolved 32 packages in 205ms
Prepared 23 packages in 100ms
Installed 30 packages in 24ms
 + annotated-types==0.7.0
 + certifi==2026.4.22
 + charset-normalizer==3.4.7
 + click==8.1.8
 + click-option-group==0.5.9
 + deprecated==1.3.1
 + dotty-dict==1.3.1
 + gitdb==4.0.12
 + gitpython==3.1.50
 + idna==3.15
 + importlib-resources==6.5.2
 + jinja2==3.1.6
 + markdown-it-py==4.2.0
 + markupsafe==3.0.3
 + mdurl==0.1.2
 + pydantic==2.13.4
 + pydantic-core==2.46.4
 + pygments==2.20.0
 + python-gitlab==6.5.0
 + python-semantic-release==10.5.3
 + requests==2.34.0
 + requests-toolbelt==1.0.0
 + rich==14.3.4
 + shellingham==1.5.4
 + smmap==5.0.3
 + tomlkit==0.13.3
 + typing-extensions==4.15.0
 + typing-inspection==0.4.2
 + urllib3==2.7.0
 + wrapt==2.1.2

this command will add python-semantic-release to the [tool.uv.dev-dependencies] section of your pyproject.toml file, ensuring that it’s only installed in development environments.

Configure Python Semantic Release

To configure python-semantic-release, we will add a [tool.semantic_release] section to your pyproject.toml file.

[tool.semantic_release]
branch = "main"
commit_parser = "conventional"
version_toml = ["pyproject.toml:project.version"]

This tells PSR:

Important naming note:

Create a new repository

Create a new empty repository in Github and name it my-package-with-semver.

and commit and push your code to the new repository:

git add .
git commit -m "feat: setup project with semantic release"
git branch -M main
git remote add origin https://github.com/mcallara/my-package-with-semver.git
git push -u origin main
[main (root-commit) 4ecee4e] feat: setup project with semantic release
 6 files changed, 575 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 .python-version
 create mode 100644 README.md
 create mode 100644 main.py
 create mode 100644 pyproject.toml
 create mode 100644 uv.lock
Enumerating objects: 8, done.
Counting objects:  12% (1/8)
Counting objects:  25% (2/8)
Counting objects:  37% (3/8)
Counting objects:  50% (4/8)
Counting objects:  62% (5/8)
Counting objects:  75% (6/8)
Counting objects:  87% (7/8)
Counting objects: 100% (8/8)
Counting objects: 100% (8/8), done.
Delta compression using up to 4 threads
Compressing objects:  16% (1/6)
Compressing objects:  33% (2/6)
Compressing objects:  50% (3/6)
Compressing objects:  66% (4/6)
Compressing objects:  83% (5/6)
Compressing objects: 100% (6/6)
Compressing objects: 100% (6/6), done.
Writing objects:  12% (1/8)
Writing objects:  25% (2/8)
Writing objects:  37% (3/8)
Writing objects:  50% (4/8)
Writing objects:  62% (5/8)
Writing objects:  75% (6/8)
Writing objects:  87% (7/8)
Writing objects: 100% (8/8)
Writing objects: 100% (8/8), 26.00 KiB | 8.67 MiB/s, done.
Total 8 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To https://github.com/mcallara/my-package-with-semver.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

Note that uv does not automatically create a Git repository for you, so you need to initialize it yourself (no need to run git init). Also, make sure to replace mcallara with your actual GitHub username in the remote URL.

Determining Version Bumps with Commit Messages

To correctly determine version bumps, python-semantic-release relies on the commit messages in your repository. By following a certain commit message format (by default, the conventional commit format), you can ensure that PSR accurately identifies the type of changes made and increments the version number accordingly.

The conventional commit format typically includes a type (e.g., fix, feat, BREAKING CHANGE), an optional scope, and a description of the change. For example:

python-semantic-release will analyze your commit history and determine the appropriate version bump based on these commit messages. This allows for a more automated and consistent release process, as the version number will be updated according to the significance of the changes made in each commit.

To actually perform a release with this information, python-semantic-release offers the version command, which calculates the next version number based on the commit history and updates the version in your pyproject.toml file, creates a new git tag, and generates a changelog entry.

You can test version calculation locally without creating a real release by using the --noop flag, which simulates the release process and shows what would happen without making any changes.

uv run semantic-release -vv --noop version --print
warning: `VIRTUAL_ENV=/home/runner/work/msdp-book/msdp-book/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead
[14:09:35] DEBUG    logging level set to: DEBUG                      ]8;id=1446565;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/commands/main.py\main.py]8;;\:]8;id=1446566;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/commands/main.py#130\130]8;;\
🛡 You are running in no-operation mode, because the '--noop' flag was supplied
           DEBUG    global cli options:                              ]8;id=1446572;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/commands/main.py\main.py]8;;\:]8;id=1446573;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/commands/main.py#142\142]8;;\
                    GlobalCommandLineOptions(noop=True, verbosity=2,            
                    config_file='pyproject.toml', strict=False)                 
           INFO     Loading configuration from pyproject.toml         ]8;id=1446580;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/util.py\util.py]8;;\:]8;id=1446581;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/util.py#77\77]8;;\
           DEBUG    Trying to parse configuration pyproject.toml in   ]8;id=1446587;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/util.py\util.py]8;;\:]8;id=1446588;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/util.py#80\80]8;;\
                    TOML format                                                 
           INFO     Using group 'main' options, as '(main|master)' ]8;id=1446595;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/config.py\config.py]8;;\:]8;id=1446596;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/config.py#590\590]8;;\
                    matches 'main'                                              
           DEBUG    Parsing git url                               ]8;id=1446603;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/helpers.py\helpers.py]8;;\:]8;id=1446604;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/helpers.py#245\245]8;;\
                    'https://github.com/mcallara/my-package-with-               
                    semver.git'                                                 
           DEBUG    setting up default session authentication         ]8;id=1446611;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/hvcs/util.py\util.py]8;;\:]8;id=1446612;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/hvcs/util.py#49\49]8;;\
           DEBUG    inverted tag_format 'v{version}' to         ]8;id=1446619;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/translator.py\translator.py]8;;\:]8;id=1446620;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/translator.py#44\44]8;;\
                    'v(?P<version>.+)'                                          
           DEBUG    Adding redact pattern                   ]8;id=1446627;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/masking_filter.py\masking_filter.py]8;;\:]8;id=1446628;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/masking_filter.py#34\34]8;;\
                    ''context.hvcs_client.token'' to                            
                    redact_patterns                                             
           DEBUG    Adding redact pattern                   ]8;id=1446633;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/masking_filter.py\masking_filter.py]8;;\:]8;id=1446634;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/masking_filter.py#34\34]8;;\
                    ''context.hvcs_client.token'' to                            
                    redact_patterns                                             
           DEBUG    is_forced_prerelease: as_prerelease = False,   ]8;id=1446641;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/commands/version.py\version.py]8;;\:]8;id=1446642;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/cli/commands/version.py#71\71]8;;\
                    forced_level_bump = None, prerelease = False                
           DEBUG    attempting to parse string '0.0.0' as Version ]8;id=1446649;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/version.py\version.py]8;;\:]8;id=1446650;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/version.py#116\116]8;;\
           DEBUG    version string 0.0.0 parsed as a              ]8;id=1446656;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/version.py\version.py]8;;\:]8;id=1446657;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/version.py#140\140]8;;\
                    non-prerelease                                              
           DEBUG    parsed build metadata '' from version string  ]8;id=1446663;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/version.py\version.py]8;;\:]8;id=1446664;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/version.py#143\143]8;;\
                    0.0.0                                                       
           INFO     found 0 previous tags                        ]8;id=1446671;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py\algorithm.py]8;;\:]8;id=1446672;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py#60\60]8;;\
           INFO     No full releases found in this branch's     ]8;id=1446678;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py\algorithm.py]8;;\:]8;id=1446679;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py#298\298]8;;\
                    history                                                     
           INFO     The latest release in this branch's history ]8;id=1446685;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py\algorithm.py]8;;\:]8;id=1446686;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py#328\328]8;;\
                    was 0.0.0                                                   
           INFO     Found 1 commits since the last release!     ]8;id=1446692;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py\algorithm.py]8;;\:]8;id=1446693;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py#340\340]8;;\
           DEBUG    parsed the following distinct levels from   ]8;id=1446699;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py\algorithm.py]8;;\:]8;id=1446700;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py#378\378]8;;\
                    the commits since the last release:                         
                    {<LevelBump.MINOR: 3>}                                      
           INFO     The type of the next release release is:    ]8;id=1446706;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py\algorithm.py]8;;\:]8;id=1446707;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py#384\384]8;;\
                    minor                                                       
           DEBUG    Bumping major version as 0.x.x versions are ]8;id=1446713;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py\algorithm.py]8;;\:]8;id=1446714;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py#139\139]8;;\
                    disabled because of                                         
                    allow_zero_version=False                                    
           DEBUG    prerelease=False and the latest version     ]8;id=1446720;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py\algorithm.py]8;;\:]8;id=1446721;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py#155\155]8;;\
                    0.0.0 is not a prerelease                                   
           DEBUG    Bumping 0.0.0 with a major bump             ]8;id=1446727;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py\algorithm.py]8;;\:]8;id=1446728;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py#230\230]8;;\
           DEBUG    performing a major level bump                 ]8;id=1446734;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/version.py\version.py]8;;\:]8;id=1446735;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/version.py#227\227]8;;\
           DEBUG    Incremented 0.0.0 to 1.0.0                  ]8;id=1446741;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py\algorithm.py]8;;\:]8;id=1446742;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py#240\240]8;;\
1.0.0
           INFO     found 0 previous tags                        ]8;id=1446747;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py\algorithm.py]8;;\:]8;id=1446748;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/version/algorithm.py#60\60]8;;\
           DEBUG    getting repository owner and name from         ]8;id=1446755;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/hvcs/github.py\github.py]8;;\:]8;id=1446756;file:///home/runner/work/msdp-book/home/ch52/my-package-with-semver/.venv/lib/python3.13/site-packages/semantic_release/hvcs/github.py#199\199]8;;\
                    environment variables                                       

Let’s unpack what PSR is telling us here, line by line:

In short: PSR read our config, looked at the commit history, classified the only commit as a feature, applied the allow_zero_version=False rule, and concluded that the next release should be 1.0.0. Because of --noop, nothing was changed on disk or in Git.

Setup GitHub Actions to use Python Semantic Release

We don’t want to run python-semantic-release manually every time we want to make a release. Instead, we can automate the process using GitHub Actions, which will run the release workflow whenever we push changes to the main branch.

This workflow will automatically determine the next version number based on the commit messages, update the version in pyproject.toml, and create a new release on GitHub.

We will create a workflow file named .github/workflows/release.yml with the following steps:

name: Semantic Release

on:
  push:
    branches:
      - main

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Checkout the code
        uses: actions/checkout@main
        with:
          fetch-depth: 0

      - name: Set up uv
        uses: astral-sh/setup-uv@main

      - name: Run Semantic Release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: uv run semantic-release version

After adding the workflow file, commit and push your changes to the main branch to trigger the workflow.

git add .
git commit -m "feat: add GitHub Actions workflow for semantic release"
git push -u origin main
[main 0187f31] feat: add GitHub Actions workflow for semantic release
 1 file changed, 23 insertions(+)
 create mode 100644 .github/workflows/release.yml
Enumerating objects: 6, done.
Counting objects:  16% (1/6)
Counting objects:  33% (2/6)
Counting objects:  50% (3/6)
Counting objects:  66% (4/6)
Counting objects:  83% (5/6)
Counting objects: 100% (6/6)
Counting objects: 100% (6/6), done.
Delta compression using up to 4 threads
Compressing objects:  33% (1/3)
Compressing objects:  66% (2/3)
Compressing objects: 100% (3/3)
Compressing objects: 100% (3/3), done.
Writing objects:  20% (1/5)
Writing objects:  40% (2/5)
Writing objects:  60% (3/5)
Writing objects:  80% (4/5)
Writing objects: 100% (5/5)
Writing objects: 100% (5/5), 633 bytes | 633.00 KiB/s, done.
Total 5 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas:   0% (0/1)        
remote: Resolving deltas: 100% (1/1)        
remote: Resolving deltas: 100% (1/1), completed with 1 local object.        
To https://github.com/mcallara/my-package-with-semver.git
   4ecee4e..0187f31  main -> main
branch 'main' set up to track 'origin/main'.

Checking that the workflow runs correctly

  1. Open the repository on GitHub.

  2. Open the Actions tab in GitHub.

  3. Wait for the workflow to finish.

  4. Confirm the version bump, tag, and GitHub release.

Expected outputs:

Since the workflow generates a new commit on the remote repository, you need to pull the latest changes to see the new commit and updated pyproject.toml in your local repository.

git pull
Already up to date.

If you add more local commits and push them without pulling the latest changes, you may encounter a merge conflict because the workflow’s commit will be ahead of your local branch. To avoid this, always pull the latest changes before making new commits.

If you created more local commits without pulling, you can resolve the merge conflict by rebasing your commits on top of the latest changes from the remote repository. Here’s how you can do that:

git pull --rebase origin main

Updating the lockfile in the workflow

If we check the lock file that appears in the repository, we can see that the version of our package in the lock file does not match the new version generated by PSR. This is because the workflow only updates the version in pyproject.toml and creates a new commit, but it does not update the lock file. To ensure that the lock file is updated with the new version, we can add a step in the workflow to run uv lock after the version bump. This will regenerate the lock file with the correct version.

Replace the Run Semantic Release step in the workflow with the following steps:

        - name: Run Semantic Release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          uv run semantic-release -v version --skip-build --no-commit --no-tag --no-changelog
          uv lock
          git add uv.lock
          uv run semantic-release -v version

This will first run the version command but only update the version in pyproject.toml without creating a commit or tag. Then it will update the lock file, add it to the staging area, and finally run the version command again to create the commit and tag with the updated version.

Testing the workflow with new commits

To see how the workflow handles different types of commits, you can make additional commits with different conventional commit messages and push them to the main branch.

For example, let’s say we commit two bug fix and a new feature:

git pull
git commit --allow-empty -m "fix: correct a minor bug"
git commit --allow-empty -m "fix: correct another minor bug"
git commit --allow-empty -m "feat: add a new feature"
git push

This will trigger the workflow again, and you should see a new release with a minor version bump (e.g., 1.1.0) because of the feat: commit, while the fix: commits will contribute to the patch level but won’t trigger a patch release on their own since they are overshadowed by the feature commit.

To see a MAJOR release, you can use a commit message that indicates a breaking change. For example:

git pull
git commit --allow-empty -m 'feat!: introduce a breaking change'
git push

This will trigger a major release (e.g., 2.0.0)

Finally, to see a patch release, you can make a commit with a fix: message without any feat: or breaking change commits:

git pull
git commit --allow-empty -m "fix: correct a minor bug"
git push

This will trigger a patch release (e.g., 2.0.1).