Ship releases without leaving the PR¶
scriv-release opens a "Changelog Preview" PR when fragments are pending, then tags a release the moment that PR is merged. It's the Changesets workflow, brought to Python on top of scriv. New to that pattern? Read The changesets pattern for the why.
Read the quickstart View on GitHub
The problem¶
Shipping a release keeps three things in sync — the version in your source, the changelog, and the tag — across many PRs from many authors. The conventional approaches force a tradeoff:
- Edit
CHANGELOG.mddirectly → every PR conflicts at the top of the file. - Parse commit messages (Conventional Commits,
semantic-release, …) → squash and rebase mangle the format, merge commits introduce subjects nobody wrote, and reviewers end up policing prose instead of code.
scriv-release records each change as its own file in changelog.d/ (no conflicts) and decides releases from working-tree state (survives any merge style). The longer story — and why this is nicer than the alternatives — is in The changesets pattern.
How it works¶
-
1. Author drops a fragment
Each PR runs
scriv createand ships a small markdown file underchangelog.d/categorizing the change. -
2. Action opens a preview PR
When fragments land on
main,scriv-releasecollects them, computes the next version from the categories, and opens (or updates) a single "Changelog Preview" PR. -
3. Merging the preview tags the release
Merging the preview removes the fragments. The next run sees that, infers the bump level from
HEAD~1, and pushes the tag.
Why scriv-release¶
-
Survives squash, rebase, and merge
Detection is file-presence based — not commit-message scraping — so any merge style works and history rewrites don't break it.
-
Bring your own version source
Pluggable providers for
bump-my-version,hatch,uv, or a custom shell command. Register your own via entry points. -
Doesn't fight your toolchain
Installs into a private venv under
$RUNNER_TEMP. Nosetup-python, no site-packages pollution, no clash with jobs that already have a Python set up.
Drop-in workflow¶
Add .github/workflows/release.yml:
on:
push:
branches: [main]
permissions: {}
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
- uses: whitphx/scriv-release@v0.3.0
with:
app-id: ${{ vars.RELEASE_APP_ID }}
app-private-key: ${{ secrets.RELEASE_APP_KEY }}
The full walkthrough — scriv config, category-to-semver mapping, version-provider choice, author flow — lives in the Quickstart.
Set up the GitHub App¶
The action references two secrets, RELEASE_APP_ID and RELEASE_APP_KEY, that come from a GitHub App you own. The App mints a short-lived installation token so the tag-push the action does at release time can trigger your downstream workflows — the default GITHUB_TOKEN deliberately can't, to avoid recursion.
scriv-release ships a manifest so you can register a pre-configured App in one click:
The page walks you through generating the key, installing the App on the repo, and wiring the secrets. See Token setup for the manual fallback and the rationale.