Skip to content

CI Integration

Cursus is designed to work seamlessly in CI. The ci subcommand auto-detects your repository state and runs the appropriate action, and the verify subcommand ensures PRs include changesets.

Terminal window
cursus ci --no-interactive

This inspects the repository and decides what to do:

StateAction
Pending changeset files existRuns prepare
No changesets, but packages have versions without matching Git tagsRuns publish
Neither conditionNo-op (exits successfully)

This makes your CI pipeline simple — just run cursus ci on every push to your main branch and it does the right thing.

Use the verify subcommand to enforce that every PR includes at least one changeset:

Terminal window
cursus verify --no-interactive

Exit codes:

  • 0 — changeset(s) found
  • 1 — error
  • 2 — no changesets found

By default, verify compares against origin/HEAD. To use a different base:

Terminal window
cursus verify --no-interactive --base origin/main
name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write # create tags and GitHub Releases
id-token: write # trusted publishing
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
# Exchange the GitHub Actions OIDC token for a short-lived CARGO_REGISTRY_TOKEN.
# Only consumed when cursus ci dispatches to publish; harmless on prepare runs.
- uses: rust-lang/crates-io-auth-action@v1
- run: cursus ci --no-interactive
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

For projects not using trusted publishing, you can omit the id-token: write permission and the exchange step, and set login tokens such as CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} in the env block instead. See the publishing guide for details on trusted publishing setup.

For PR changeset verification:

name: CI
on:
pull_request:
jobs:
verify-changeset:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- run: cursus verify --no-interactive

By default, commits made by the release workflow appear as Unverified on GitHub because they are produced by the local git binary on the runner. To get the green Verified badge on your release commits, use a GitHub App installation token instead of secrets.GITHUB_TOKEN.

When Cursus detects it is running on GitHub Actions with a token available, it automatically routes the prepare commit through the GitHub Git Data API (signed_commits = "auto" is the default). GitHub signs any commit created this way using its web-flow GPG key — but this signing only activates when the request is authenticated as a GitHub App, not a personal access token or the default GITHUB_TOKEN.

  1. Go to Settings → Developer settings → GitHub Apps → New GitHub App.
  2. Give it a name (e.g. my-org-release-bot) and uncheck Webhook active.
  3. Under Repository permissions, set Contents and Pull Requests to Read and write.
  4. Install the app on your repository.
  5. Note the App ID from the app’s settings page.
  6. Generate a private key (downloaded as a .pem file).
  7. Find the app’s user ID: visit https://api.github.com/users/{app-name}[bot] and note the id field.

Store these in your repository:

ItemWhere
App IDRepository variable APP_ID
Private key (.pem contents)Repository secret APP_PRIVATE_KEY
User IDRepository variable APP_USER_ID
name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
permissions:
id-token: write # trusted publishing
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: actions/checkout@v6
with:
token: ${{ steps.app-token.outputs.token }}
fetch-depth: 0
# Tags are still created via the local git binary during publish, so set
# the identity here to attribute them to the App bot rather than the
# default runner identity. The release commit itself does not need this —
# its committer is set by GitHub when the API commit is created.
- name: Configure git identity
run: |
git config user.name "${{ steps.app-token.outputs.app-slug }}[bot]"
git config user.email "${{ vars.APP_USER_ID }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com"
- uses: rust-lang/crates-io-auth-action@v1
- run: cursus ci --no-interactive
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}

The contents: write permission is no longer needed in the workflow because the App token carries it on behalf of the installation. No changes to .cursus/config.toml are required — signed_commits = "auto" is the default.

For PRs created by tools like Renovate or Dependabot, you can automatically derive a changeset from the Conventional Commit message. This works best with git integration enabled, which lets Cursus commit and push the changeset back to the PR branch without any extra steps:

name: Auto Changeset
on:
pull_request:
types: [opened, synchronize]
jobs:
changeset:
if: contains(github.event.pull_request.labels.*.name, 'dependencies')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: cursus change --no-interactive --auto