Skip to content

Release Automation

In release mode, the action consumes changesets and opens a “Version Contracts” PR on the main branch.


  1. The Action checks for changeset files in .contractual/changesets/
  2. If none exist, the job exits with no changes
  3. If changesets exist, contractual version consumes them
  4. A pull request titled “Version Contracts” is opened or updated
  5. Merge that PR to complete the release

name: Contractual Release
on:
push:
branches: [main]
paths:
- '.contractual/changesets/**'
permissions:
contents: write
pull-requests: write
jobs:
contractual-release:
name: Version contracts
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Contractual
id: release
uses: contractual-dev/action@v1
with:
mode: release
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Log version PR URL
if: steps.release.outputs.version-pr-url != ''
run: echo "Version PR: ${{ steps.release.outputs.version-pr-url }}"

InputDescriptionDefault
modeMust be release
github-tokenToken for PR creation and commits${{ github.token }}
config-pathPath to contractual.yaml./contractual.yaml
version-pr-titleTitle for the Version Contracts PRVersion Contracts
version-pr-branchBranch for the Version Contracts PRcontractual/version-contracts
pre-release-tagPre-release tag (e.g., beta, rc)
create-releasesCreate GitHub Releases when Version PR mergestrue
tag-prefixTag format: contract, v, or nonecontract
attach-specsAttach spec files to GitHub Releasestrue

OutputDescription
version-pr-urlURL of the Version Contracts PR
bumped-versionsJSON of bumped versions, e.g. {"orders-api": "2.0.0"}
release-urlsJSON array of created GitHub Release URLs
created-tagsJSON array of created git tags

- uses: contractual-dev/action@v1
with:
mode: release
version-pr-title: "chore: release contract versions"
version-pr-branch: "release/contractual"

Use a PAT when the Version PR needs to trigger other workflows:

- uses: contractual-dev/action@v1
with:
mode: release
github-token: ${{ secrets.CONTRACTUAL_PAT }}
- uses: contractual-dev/action@v1
id: release
with:
mode: release
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Notify Slack
if: steps.release.outputs.version-pr-url != ''
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-d '{"text": "Version PR ready: ${{ steps.release.outputs.version-pr-url }}"}'

When multiple PRs merge before a release, each contributes a changeset. At release time:

  • For each contract, the highest bump level wins (major > minor > patch)
  • All changeset bodies are concatenated into the changelog
fuzzy-lion-dances.md: orders-api: minor
silver-hawk-runs.md: orders-api: major
brave-wolf-leaps.md: orders-api: patch

Result: orders-api gets a major bump.


By default, GitHub Actions created PRs don’t trigger other workflows. Options:

  1. Use a PAT: PRs created with a PAT trigger workflows
  2. Use a GitHub App: Same effect without storing a PAT
  3. Manually trigger: Merge the Version PR to trigger downstream

The release workflow only triggers when changesets are pushed:

on:
push:
branches: [main]
paths:
- '.contractual/changesets/**'

This prevents unnecessary runs when other files change.


When the Version Contracts PR merges, the action automatically:

  1. Creates a git tag for each bumped contract
  2. Creates a GitHub Release with release notes
  3. Attaches the spec file as a release asset

To trigger on Version PR merge, add versions.json to the paths filter:

on:
push:
branches: [main]
paths:
- '.contractual/changesets/**'
- '.contractual/versions.json'
tag-prefixExample tag
contract (default)orders-api@2.0.0
vv2.0.0
none2.0.0
- uses: contractual-dev/action@v1
id: release
with:
mode: release
- name: Notify Slack on release
if: steps.release.outputs.created-tags != '[]'
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-d '{"text": "Released: ${{ steps.release.outputs.created-tags }}"}'

To create only git tags without GitHub Releases:

- uses: contractual-dev/action@v1
with:
mode: release
create-releases: false
- uses: contractual-dev/action@v1
with:
mode: release
attach-specs: false

Create pre-release versions (e.g., 2.0.0-beta.0) with the pre-release-tag input:

- uses: contractual-dev/action@v1
with:
mode: release
pre-release-tag: beta

Pre-release versions:

  • Are marked as pre-release in GitHub Releases
  • Follow semver pre-release conventions (2.0.0-beta.0, 2.0.0-beta.1)
  • Increment the pre-release number on subsequent releases with the same tag