Skip to content

GitHub Actions

Craft provides GitHub Actions for automating releases and previewing changelog entries in pull requests.

For a real-world example of using Craft’s GitHub Actions, see the getsentry/publish repository.

Craft offers two ways to automate releases in GitHub Actions:

Option Best For Flexibility
Reusable Workflow Quick setup, standard release flow Low - runs as a complete job
Composite Action Custom workflows, pre/post steps High - composable with other steps

The simplest way to set up Craft releases. Call the workflow and let it handle everything:

name: Release
on:
workflow_dispatch:
inputs:
version:
description: 'Version to release (or "auto")'
required: false
jobs:
release:
permissions:
contents: write
uses: getsentry/craft/.github/workflows/release.yml@v2
with:
version: ${{ inputs.version }}
secrets: inherit
Input Description Default
version Version to release. Can be a semver string (e.g., “1.2.3”), a bump type (“major”, “minor”, “patch”), or “auto” for automatic detection. Uses versioning.policy from config
merge_target Target branch to merge into. Default branch
force Force a release even when there are release-blockers. false
blocker_label Label that blocks releases. release-blocker
publish_repo Repository for publish issues (owner/repo format). Use "self" to create issues in the source repository itself. {owner}/publish
git_user_name Git committer name. GitHub actor
git_user_email Git committer email. Actor’s noreply email
path The path that Craft will run inside. .
craft_config_from_merge_target Use the craft config from the merge target branch. false
Output Description
version The resolved version being released
branch The release branch name
sha The commit SHA on the release branch
previous_tag The tag before this release (for diff links)
changelog The changelog for this release

Use the action directly when you need to add custom steps before or after the release, or integrate Craft into a more complex workflow:

name: Release
on:
workflow_dispatch:
inputs:
version:
description: 'Version to release (or "auto")'
required: false
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Custom pre-release steps
- run: echo "Running pre-release checks..."
- uses: getsentry/craft@v2
with:
version: ${{ github.event.inputs.version }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Custom post-release steps
- run: echo "Release prepared!"

The action accepts the same inputs and produces the same outputs as the reusable workflow.

The Action uses the matching standalone executable when the selected Craft release includes one. Craft’s own release workflow prefers the native build artifact from the same run. Versions before standalone binaries and GHCR nightlies retain the JavaScript compatibility path, so Node remains required by the composite Action. The Action invokes the same install script served on the website. That script selects the source, detects GITHUB_ACTIONS or CI, and skips shell completions and agent skill setup automatically. Missing native release assets use the JavaScript compatibility path; other download or verification failures stop installation. Native candidates must report the selected version before placement.

Enable summaries with changelog.aiSummary: {}. The default connection is Copilot Responses, gpt-5.4-nano and the GITHUB_TOKEN environment variable. The model must be available to the supplied credential. Use aiSummary: false to disable generation.

For a custom provider, pass llm JSON and map a secret to llm_api_key:

- uses: getsentry/craft@v2
env:
GITHUB_TOKEN: ${{ github.token }} # release operations
with:
llm: '{"endpoint":"https://gateway.example.com/v1/chat/completions","model":"your-model"}'
llm_api_key: ${{ secrets.TEAM_MODEL_TOKEN }}

llm_api_key selects a dedicated environment variable over llm.apiKeyEnv; it never replaces the release token. Alternatively, map a secret to your configured apiKeyEnv. Craft reads environment variables, not GitHub’s secret store. Secrets must be available to the workflow’s repository; an organization secret can grant selected-repository access.

Pass repository/organization configuration as llm: ${{ vars.CRAFT_AI_LLM }}. The reusable release workflow supplies this fallback and forwards its llm input and optional llm_api_key secret to both Action paths.

For built-in-token Copilot access, callers (including reusable workflow callers) must grant copilot-requests: write alongside their release permissions. The organization must enable “Allow use of Copilot CLI billed to the organization.” Usage is billed to the organization; permissions alone do not enable access. Craft’s own release workflow keeps its release-bot token for Git operations and passes the workflow token separately for AI requests. An explicit llm_api_key secret takes precedence.

Map ${{ github.token }} to GITHUB_TOKEN, or pass it as llm_api_key when release operations use a different token. See GitHub’s workflow-token guide. Personal Copilot credentials require Copilot access; fine-grained PATs need Copilot Requests: write. Custom provider credentials need no Copilot permission.

Copilot’s direct HTTP backend is outside GitHub’s public REST API contract. Missing credentials or failed requests preserve the original changelog without switching providers. See AI summary configuration for precedence, limits and output examples.

The AI summary integration workflow makes one live request using public fixture facts when the AI connection/release wiring changes in an internal pull request. Fork pull requests skip that credentialed check. Regular AI unit, HTTP-fixture, and CLI/Action integration tests still run in the Node 22/24 CI suites. A green release with fallback warnings alone does not prove live AI summaries worked.

Every successful push build on Craft’s master branch publishes its binary to ghcr.io/getsentry/craft as an OCI artifact. The nightly tag advances when the current master build finishes publishing; nightly-<full commit SHA> keeps each build available after master advances. These artifact tags are separate from the package’s Docker image tags.

Pin the action to a master commit to test that exact build. When the action’s package.json declares a development version, the action automatically downloads nightly-<action commit SHA>:

- uses: getsentry/craft@<full-master-commit-sha>
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

To use the newest successful master build with another action ref, set craft_version: nightly. You can also set craft_version: nightly-<full commit SHA> to select a specific build. The reusable Release workflow forwards its craft_version input to the action too. Nightlies are available only after the Publish Craft nightly to GHCR job succeeds. Missing or invalid artifacts stop the action; it never substitutes a different build or the latest stable release. Published action releases continue to select their matching stable Craft release by default.

When using auto-versioning, Craft analyzes conventional commits to determine the version bump. This works with both the workflow and the action:

# Using the reusable workflow
name: Auto Release
on:
schedule:
- cron: '0 10 * * 1' # Every Monday at 10 AM
jobs:
release:
permissions:
contents: write
uses: getsentry/craft/.github/workflows/release.yml@v2
with:
version: auto
secrets: inherit

The changelog preview workflow posts a comment on pull requests showing how they will appear in the changelog. This helps contributors understand the impact of their changes.

Call the reusable workflow from your repository:

name: Changelog Preview
on:
pull_request_target:
types: [opened, synchronize, reopened, edited, labeled, unlabeled]
permissions:
contents: read
pull-requests: write
statuses: write
jobs:
changelog-preview:
uses: getsentry/craft/.github/workflows/changelog-preview.yml@v2
secrets: inherit
Input Description Default
working-directory Directory to run Craft in (relative to repo root) .
craft-version Version of Craft to use (tag or “latest”) latest
comment Post changelog as PR comment (true) or as commit status with job summary (false) true

The workflow supports two output modes for displaying changelog previews:

Posts the changelog preview as a PR comment that updates automatically:

jobs:
changelog-preview:
uses: getsentry/craft/.github/workflows/changelog-preview.yml@v2
with:
comment: true # or omit for default
secrets: inherit

Pros:

  • Changelog visible directly on PR page
  • All team members see updates immediately
  • Familiar commenting interface

Cons:

  • Creates notification noise on every update
  • Multiple updates trigger multiple notifications
  • Can clutter PR conversation on active branches

Creates a commit status with the semver impact and writes the full changelog to the Actions job summary:

jobs:
changelog-preview:
uses: getsentry/craft/.github/workflows/changelog-preview.yml@v2
with:
comment: false
secrets: inherit

Pros:

  • Minimal notification noise
  • Cleaner PR interface
  • Semver impact visible in status checks section
  • Full changelog available in Actions job summary

Cons:

  • Requires clicking through to Actions run to see full changelog
  • Less immediate visibility than comment

If one repository releases several packages from separate Craft configs, call the workflow once per package with its working-directory. Each directory gets its own PR comment and its own status context, and the heading names the directory, e.g. Semver Impact of This PR (packages/python). A PR that touches several packages shows one preview per package:

jobs:
changelog-preview-python:
uses: getsentry/craft/.github/workflows/changelog-preview.yml@v2
with:
working-directory: packages/python
changelog-preview-rust:
uses: getsentry/craft/.github/workflows/changelog-preview.yml@v2
with:
working-directory: packages/rust

Previews run from the repository root keep the plain Changelog Preview / Semver Impact status context. In directory names, characters other than letters, digits, ., _, / and - are replaced with -, and a short hash is appended so that, for example, foo bar and foo-bar still get separate previews. Directory names with uppercase letters also get the hash, because GitHub compares status contexts case-insensitively.

on:
pull_request_target:
types: [opened, synchronize, reopened, edited, labeled, unlabeled]
permissions:
contents: read
pull-requests: write
statuses: write
jobs:
changelog-preview:
uses: getsentry/craft/.github/workflows/changelog-preview.yml@v2
with:
craft-version: '2.15.0'
secrets: inherit
  1. Generates the changelog - Runs craft changelog --pr <number> --format json to generate the upcoming changelog with metadata
  2. Fetches PR info - Gets PR title, body, labels, and base branch from GitHub API
  3. Categorizes the PR - Matches the PR to changelog categories based on labels and commit patterns
  4. Suggests version bump - Based on matched categories with semver fields (major/minor/patch)
  5. Highlights PR entries - The current PR is rendered with blockquote style (displayed with a left border in GitHub)
  6. Displays the preview - Posts as a PR comment (default) or creates a neutral check run with job summary (when comment: false)
  7. Auto-updates - The preview is automatically updated when you update the PR (push commits, edit title/description, or change labels)

The workflow posts a comment like this:

## Suggested Version Bump
🟡 **Minor** (new features)
## 📋 Changelog Preview
This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).
---
### New Features ✨
> - feat(api): Add new endpoint by @you in #123
- feat(core): Existing feature by @other in #100
### Bug Fixes 🐛
- fix(ui): Resolve crash by @other in #99
---
🤖 This preview updates automatically when you update the PR.

The workflow supports these PR event types:

  • opened - When a PR is created
  • synchronize - When new commits are pushed
  • reopened - When a closed PR is reopened
  • edited - When the PR title or description is changed
  • labeled - When labels are added or removed

The workflow requires specific permissions and secrets to function correctly:

Permissions (all three required):

permissions:
contents: read
pull-requests: write
statuses: write
  • contents: read - Allows the workflow to checkout your repository and read git history for changelog generation
  • pull-requests: write - Required for comment mode to post and update comments on pull requests
  • statuses: write - Required for status check mode to create commit statuses

Secrets:

  • secrets: inherit - Passes your repository’s GITHUB_TOKEN to the workflow, ensuring it has access to your repository (especially important for private repositories)

Repository Setup:

  • The repository should have a git history with tags for the changelog to be meaningful

Use #skip-changelog in your commit message or PR body to exclude a commit from the changelog:

chore: Update dependencies
#skip-changelog

You can configure labels to exclude PRs from the changelog. In your .craft.yml:

changelog:
categories:
- title: 'New Features ✨'
labels: ['feature', 'enhancement']
- title: 'Bug Fixes 🐛'
labels: ['bug', 'fix']
exclude:
labels: ['skip-changelog', 'dependencies']
authors: ['dependabot[bot]', 'renovate[bot]']

PRs with the skip-changelog label or from excluded authors will not appear in the changelog.

You can use both the changelog preview and release workflows together for a complete release flow:

.github/workflows/changelog-preview.yml
name: Changelog Preview
on:
pull_request_target:
types: [opened, synchronize, reopened, edited, labeled, unlabeled]
permissions:
contents: read
pull-requests: write
statuses: write
jobs:
changelog-preview:
uses: getsentry/craft/.github/workflows/changelog-preview.yml@v2
secrets: inherit
.github/workflows/release.yml
name: Release
on:
workflow_dispatch:
inputs:
version:
description: 'Version (leave empty for auto)'
required: false
jobs:
release:
permissions:
contents: write
uses: getsentry/craft/.github/workflows/release.yml@v2
with:
version: ${{ inputs.version || 'auto' }}
secrets: inherit