Automating releases with release-please

By ainame / Published on 20 September 2025

This year I began to enjoy creating OSS in my spare time, because agentic coding with AI became practical.

Releasing new versions is still hard work. Recently I have been building CLIs, so tagging in Git is not enough. As soon as I tag, I want to run the steps that publish the tool. To make that easier I introduced release-please into ainame/tuzuru, and I would like to share how it went.

Before release-please

When I develop a tool with pushing straight to main, I created a simple script for releasing scripts/release.sh. With the GitHub Actions workflow, it worked like this:

  • Run scripts/release.sh on my local
    • Build and test a tool
    • If it succeeds, update the files that hold the version, and git tag and apush
  • GitHub notices the tag and builds
    • Create the universal binary for macOS
    • Create the statically linked binary for Linux
    • Create the GitHub Release and upload the pre-built binaries
    • Publish the npm package that wraps the binary
    • Update the Homebrew formula

This flow worked fine, but once the tool felt stable I wanted to switch to PR-based development. I tried to build my own flow with Claude Code and friends, but the default token that GitHub Actions provides has limits, so the approach did not land nicely. I looked for existing examples that solved the same problem, and that is when I found release-please.

What is release-please?

It is a CLI tool written in TypeScript. If you want to use it on GitHub Actions, you can just call release-please-action in your workflow. I took me a while to understand how it works. I tried it and figured it out. I’m going to share what I learned here. (I have not read through the source code though, so please let me know if I got something wrong.)

How it works

As the README suggests, you may start putting f.github/workflows/release-please.yml to your project. With the workflow like below, release-please runs every time something is merged into main.

on:
  push:
    branches:
      - main

permissions:
  contents: write
  issues: write
  pull-requests: write

name: release-please

jobs:
  release-please:
    runs-on: ubuntu-latest
    steps:
      - uses: googleapis/release-please-action@v4
        with:
          # this assumes that you have created a personal access token
          # (PAT) and configured it as a GitHub action secret named
          # `MY_RELEASE_PLEASE_TOKEN` (this secret name is not important).
          token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }}
          # this is a built-in strategy in release-please, see "Action Inputs"
          # for more options
          release-type: simple

release-please recognises the current release state from PR labels and switches between two behaviours:

  1. It builds a release PR based on Conventional Commits.
    • It finds the commits for the next release. If there is even one user-facing change such as feat or fix, it opens a PR.
    • It adds the autorelease: pending label to the PR.
    • It updates version strings in the codebase based on release-please-config.json and .release-please-manifest.json.
    • The release PR is updated every time something merges into main (including updates to CHANGELOG.md).
  2. When a PR with the autorelease: pending label is merged, it creates a Git tag and a GitHub Release page.
    • At that point you can trigger any workflow on the release published event.
    • Because the Release page already exists you can, for example, build binaries and upload them there.

The key is to manage the pending state through the PR label. This design reminded me of Renovate, which also uses resources on GitHub as its state.

https://github.com/ainame/tuzuru/pull/64

The release PR gathers all user-facing changes on every mereges.

Quirks

It looks neat, but release-please comes with a few habits.

  • It decides the next version from Conventional Commits (a release with only fix becomes a patch release, and so on).
  • It strongly encourages a linear Git history (squash merge). If you merge main into the PR with a merge commit, the changelog becomes messy.
  • You need a personal access token or a GitHub App token to get around the limits of the default GitHub Actions token.

If you are not used to these things, they may feel troublesome. I had never used Conventional Commits before, and it looked like a hassle. But I’m sold. it is still far better than writing useless commit messages. And perhaps, if you’re doing PR-based developement and allowing only squash merge, you only care the PR title to use conventional commits?

Wrapping up

After some trial and error, I managed to bring release-please into ainame/tuzuru, and I am happy with the streamlined release flow. Let’s give release-please a go and have a fun with coding!