Automating releases with release-please
This year I began to enjoy creating OSS in my spare time, because agentic coding with AI became practical.
- ainame/swift-slack-client – a Swift client that plays the same role as Slack Bolt
 - ainame/xcodeproj-cli – a CLI version (no Docker needed) of giginet/xcodeproj-mcp-server/
 - ainame/tuzuru – a static site generator I built to host this blog
 - ainame/swift-displaywidth – a library that calculates character widths so tuzuru can show tables in the terminal
 
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.shon 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:
- 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 
featorfix, it opens a PR. - It adds the 
autorelease: pendinglabel to the PR. - It updates version strings in the codebase based on 
release-please-config.jsonand.release-please-manifest.json. - The release PR is updated every time something merges into 
main(including updates toCHANGELOG.md). 
 - It finds the commits for the next release. If there is even one user-facing change such as 
 - When a PR with the 
autorelease: pendinglabel 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 
fixbecomes a patch release, and so on). - It strongly encourages a linear Git history (squash merge). If you merge 
maininto 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!