Guides
GitHub Action
The chamoji Action runs the CLI in your workflows. It does three jobs: it writes each pushed tag’s notes to its GitHub release, keeps one changelog preview comment up to date on every pull request, and, when you ask, cuts the whole release. It can also keep your repo card up to date.
GITHUB ACTIONSThe Action needs the whole history. Check out with fetch-depth: 0. On a
shallow clone it stops at once, in every mode, and says so.
Add it
This workflow gives you release notes and the PR preview. Commit it as .github/workflows/changelog.yml:
.github/workflows/changelog.yml
name: changelog
on:
pull_request:
push:
tags: ['v*']
permissions:
contents: write # release notes
pull-requests: write # the preview comment
jobs:
chamoji:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # chamoji reads every commit and tag
- uses: HeavenlyEntity/chamoji-ai@v0.14.0
with:
chamoji-version: '0.14.0'
@v0.14.0 pins the Action itself. The CLI it runs is a separate pin: without chamoji-version, the Action runs npx --yes @chamoji/cli@latest. The Action sets up Node 22 for the job.
Which job runs
The mode input picks the job. The default, auto, picks from the event:
- A pushed tag runs
notes. - A pull request (
pull_requestorpull_request_target) runscomment. - Any other event fails with “can’t pick a mode for the … event; set mode to notes, comment, release or card”. A
workflow_dispatchorscheduleworkflow needsmode: releaseormode: card.
Release notes
On a pushed tag, the Action writes that release’s notes and puts them on its GitHub release:
- Checks the tag. Without release units, only a tag that starts with a version, such as
v1.2.3or1.2.3, gets notes. For any other tag it says “isn’t a whole-repo version tag; leaving its release alone” and passes. - Writes the notes.
chamoji changelogfrom the previous version tag to this one, without the# Changelogtitle. The first tag gets the whole history. - Updates the GitHub release. If the tag already has a GitHub release, its notes are replaced. Otherwise
the Action creates one, titled with the tag. Without release units, it marks
it a prerelease when the tag has a hyphen anywhere, such as
v2.0.0-rc.1.
The notes also go to the job summary, and the version and notes-file outputs name the tag and the file. The job needs contents: write.
GitHub doesn’t start workflows for a tag pushed with the job’s own GITHUB_TOKEN. So a tag that mode: release pushes doesn’t run this job, and doesn’t need to: the release job creates the GitHub release itself.
The PR preview
On every pull request, the Action keeps one comment up to date. It holds the changelog lines the PR’s own commits add, and the version that merging it would make.
This is an example. It’s the text the Action’s script wrote when run on a branch of a small repo called widgets, with three commits on top of main:
The preview comment
<!-- chamoji-changelog -->
### Changelog preview
Merging this makes the next release **v0.3.0** (minor).
#### Added
- ✨ Add presets for the last 7 and 30 days (c0f4b6a)
#### Fixed
- 🐛 Clear the range when the dialog closes (5e736fc)
#### Maintenance
- ✅ Test the range presets (53f014d)
<sub>From [chamoji](https://github.com/HeavenlyEntity/chamoji-ai), updated on every push.</sub>
- The hidden first line marks the comment, so the next push updates it instead of adding another. The Action only takes over a comment with that line that
comment-authorposted, so a comment that quotes the line is left alone. - The headline is one of three. With no release tag yet: “There are no release tags yet, so this is part of the first release.” When nothing would release: “Nothing here needs a release on its own: only docs, tests, refactors or similar.” Otherwise: the next version and its bump, as above.
- The version is the one
chamoji next-versiongives at the PR’s head. So it counts the commits already on the base branch that haven’t been released yet, not just the PR’s. - The lines are only the PR’s own commits, grouped as in the changelog, with each heading one level down. A PR whose commits add no lines says “No commits in this PR add changelog lines.”
- The job summary always gets the preview too.
The job needs pull-requests: write. On a pull request from a fork, the token can’t comment. The Action then warns “couldn’t post the PR comment (a fork PR’s token can’t), so the preview is in the job summary”, and the job passes.
Releases
To release at the press of a button, add a second workflow with mode: release. This one asks for an optional version:
.github/workflows/release.yml
name: release
on:
workflow_dispatch:
inputs:
version:
description: The version to release; leave it empty to pick it from the commits
required: false
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: HeavenlyEntity/chamoji-ai@v0.14.0
with:
mode: release
release-version: ${{ inputs.version }}
chamoji-version: '0.14.0'
The Action sets the commit author to github-actions[bot] and runs chamoji release --push --json, with the release-version and the unit when you give them. That makes the CHANGELOG.md section, the version bumps, the release commit and the tag, pushes them, and creates the GitHub release. release has every step.
- Nothing to release (exit 3) passes, with “chamoji: nothing to release since the last tag” in the log and the job summary.
- Any other failure fails the job. A failed check exits 2. The branch check only runs when the clone knows
origin’s default branch, that is, whenrefs/remotes/origin/HEADexists, which depends on how the job checks out. When it runs, a release from another branch can exit 2, and the Action doesn’t pass--any-branch, so run the workflow on the default branch. - The notes of each released tag go to the job summary, and the
versionoutput names the tag. - The push uses the credential
actions/checkoutsaved, the job’sGITHUB_TOKENunless you give checkout atoken.github-tokenis only for the gh calls, such as creating the GitHub release. - A protected default branch refuses the bot’s push. Cut releases on your machine with
chamoji release --push, or giveactions/checkouta token that may push there. - Other workflows don’t start for a tag pushed with
GITHUB_TOKEN. If a separate workflow publishes your package on a tag, publish in this job instead, or giveactions/checkouta GitHub App or fine-grained token.
The repo card
mode: card saves an image of your latest release on a branch of its own, for your readme. auto never picks it, so give it a workflow of its own, or a step after the release step. Repo card has the workflow and what it sends.
Inputs
Every input of action.yml at v0.14.0, with its default:
- Name
mode- Type
- string · default auto
- Description
auto,notes,comment,releaseorcard.autopicksnotesfor a pushed tag andcommentfor a pull request, and fails for other events.
- Name
chamoji-version- Type
- string · default latest
- Description
The
@chamoji/cliversion to run with npx. Pin it, such as'0.14.0', so a new release of the CLI can’t change your notes unannounced. Ignored whencliis set.
- Name
cli- Type
- string · default empty
- Description
A command that runs chamoji instead of npx. A repo that builds chamoji itself can give
node packages/cli/dist/cli.js. Its words are split on spaces. Give chamoji itself by its full path, such as/usr/local/bin/chamoji: a barechamojidoesn’t work in the 0.14.0 Action, in any mode. With release units, the PR preview accepts only some commands: see With release units.
- Name
release-version- Type
- string · default empty
- Description
releasemode only. The version to release, such as2.0.0or2.0.0-rc.1. Empty lets chamoji pick it from the commits. With release units, it needsunit.
- Name
unit- Type
- string · default empty
- Since
- SINCE 0.13.0
- Description
releasemode only, in a repo with release units. Release just this unit. Empty releases every unit that has something to release, aschamoji releasedoes. It takes one unit.
- Name
ai- Type
- boolean · default false
- Description
truepasses--aito the CLI, so Jev sorts the commits no rule can place. It needstypesafe-api-key. Withai: trueand no key, the jobs that pass--aifail, since the CLI exits 1:notes,release, and the preview without release units.cardand the preview with release units never pass--ai, so they still run.
- Name
typesafe-api-key- Type
- string · default empty
- Description
The TypeSafe key for
ai, such as${{ secrets.TYPESAFE_API_KEY }}. The Action passes it to the CLI asTYPESAFE_API_KEY.
- Name
comment-author- Type
- string · default github-actions[bot]
- Description
commentmode only. The login thatgithub-tokenacts as, so the Action only updates its own preview comment. Set it whengithub-tokenis a personal token. It must look like a GitHub login, such asoctocatormy-app[bot], or the job fails.
- Name
card-endpoint- Type
- url · default https://chamoji.dev/api/card
- Description
cardmode only. The renderer that draws the card image.
- Name
card-branch- Type
- string · default chamoji-card
- Description
cardmode only. The branch that holds nothing butcard.png. The Action force-pushes it, so it refuses a branch that holds anything else.
- Name
github-token- Type
- string · default the job’s GITHUB_TOKEN
- Description
The token for the gh CLI, as
${{ github.token }}by default.notesandreleaseneedcontents: writeon it.commentneedspull-requests: write.carddoesn’t use it: it pushes with the credential thatactions/checkoutsaved.
Outputs
- Name
version- Type
- string
- Description
The tag that was released or given notes. With release units, every tag released, separated by commas.
- Name
notes-file- Type
- path
- Description
notesmode only. The file that holds the release notes.
- Name
card-url- Type
- url
- Description
cardmode only. The image’s raw GitHub address,https://raw.githubusercontent.com/<owner>/<repo>/<card-branch>/card.png.
Permissions
Each job needs fetch-depth: 0 on actions/checkout, and one permission:
| Job | Permission | What uses it |
|---|---|---|
notes | contents: write | Creating or editing the GitHub release |
comment | pull-requests: write | Posting and updating the preview comment |
release | contents: write | Pushing the commit and tag, and the release |
card | contents: write | Pushing the card branch |
card pushes with the credential actions/checkout leaves behind, so persist-credentials: false breaks it.
Sorting with AI in CI
To have Jev sort vague commits in the notes, the preview and releases, add a repository secret and two inputs:
with
- uses: HeavenlyEntity/chamoji-ai@v0.14.0
with:
ai: true
typesafe-api-key: ${{ secrets.TYPESAFE_API_KEY }}
ai applies to notes, release, and the preview in a repo without release units. It doesn’t apply to card, which counts what the rules sort, or to the preview in a repo with release units, which never gets your secrets. Sorting with AI says what is sent.
With release units
In a repo with release units, each job works per unit. Monorepos explains units.
- The CLI version. When
chamoji.config.jsonhas anyreleasesection, the Action first checks that the CLI is 0.13.0 or newer, and otherwise fails with “this repo’s release settings need chamoji 0.13.0 or newer; set chamoji-version”. - Release notes. On a pushed tag, the Action finds the unit whose tag format the tag fits, and writes that release’s notes with
chamoji changelog --for-tag. A tag that fits no unit fails the job. The release is a prerelease only when a hyphen follows the version’sx.y.z, as inweb/v2.0.0-rc.1, so a hyphen in a unit’s name, as inweb-v1.0.0, doesn’t count. Unit tags such asweb/v1.3.0don’t matchtags: ['v*'], so list them in the trigger, for exampletags: ['v*', '*/v*']. - Releases.
unitreleases just that unit. Without it, every unit with something to release goes in one release commit, with the tags and GitHub releases thatchamoji release --pushmakes. - The preview lists only the units the PR touches, with the release merging it would make.
This is an example of the preview, from the Action’s script run on a branch of a shop with two apps. The branch adds a mobile feature and a fix in a shared package:
The preview comment
<!-- chamoji-changelog -->
### Changelog preview
After merging, the next releases would be:
- mobile → 1.2.0
- web → 1.4.0 (rides along with its next release)
#### mobile 1.2.0
##### Added
- ✨ Add the wishlist screen (f3ec8f7)
##### Fixed
- 🐛 Keep the button label on one line (d9cadbf) (via `packages/ui`)
#### web 1.4.0
##### Fixed
- 🐛 Keep the button label on one line (d9cadbf) (via `packages/ui`)
<sub>From [chamoji](https://github.com/HeavenlyEntity/chamoji-ai), updated on every push.</sub>
“Rides along” means the PR adds only shared lines to that unit. Web’s next version is 1.4.0 because main already has a web feature waiting. A PR that adds nothing to any unit says “This PR adds nothing to any unit’s release notes.”
The preview runs safely on untrusted code. It runs the CLI in a separate checkout of the PR’s head, with a short list of environment variables and none of your secrets, and without --ai. So the cli input must be one of these:
- unset, so the Action installs
@chamoji/cliatchamoji-versionwithout running its install scripts; nodewith a built CLI file in your workspace, such asnode packages/cli/dist/cli.js;- the chamoji command by its full path, such as
/usr/local/bin/chamoji. A barechamojidoesn’t work in the 0.14.0 Action.
Anything else, such as npx, a shell wrapper or tsx, is refused, and so is an option word such as --require.