Release Process

This document describes how to create a release of pg_trickle.

Overview

Releases are fully automated via GitHub Actions. Pushing a version tag (v*) triggers the Release workflow, which:

  1. Runs a preflight version-sync check to ensure all version references match the tag
  2. Builds extension packages for Linux (amd64), macOS (arm64), and Windows (amd64)
  3. Smoke-tests the Linux artifact against a live PostgreSQL 18 instance
  4. Creates a GitHub Release with archives and SHA256 checksums
  5. Builds and pushes a multi-arch extension image to GHCR (for CNPG Image Volumes)

A separate PGXN workflow also fires on the same v* tag and publishes the source archive to the PostgreSQL Extension Network.

Prerequisites

  • Push access to the repository (or a PR merged by a maintainer)
  • All CI checks passing on main (verify the last run on the version-bump commit succeeded)
  • The version in Cargo.toml matches the tag you intend to push
  • Required GitHub secrets configured (see Required GitHub Secrets below)

Required GitHub Secrets

The release automation uses the following GitHub Actions secrets. Set them under Settings → Secrets and variables → Actions → New repository secret.

SecretUsed byDescription
PGXN_USERNAMEpgxn.ymlYour PGXN account username. Used to authenticate the curl upload to PGXN Manager when publishing source archives to the PostgreSQL Extension Network. Register at pgxn.org.
PGXN_PASSWORDpgxn.ymlPassword for the PGXN account above. Never hardcode this — it must be stored as a secret so it is never exposed in logs or committed to the repository.
CODECOV_TOKENcoverage.ymlUpload token for Codecov. Used to publish unit and E2E coverage reports. Obtain it from the Codecov dashboard after linking the repository. The workflow degrades gracefully (fail_ci_if_error: false) if absent.
BENCHER_API_TOKENbenchmarks.ymlAPI token for Bencher, the continuous benchmarking platform. Used to track Criterion benchmark results on main and detect regressions on pull requests. The benchmark steps are skipped entirely when this secret is absent, so CI still passes without it. Create a project at bencher.dev and copy the token from the project settings.

Note: The GITHUB_TOKEN secret is provided automatically by GitHub Actions and does not need to be configured manually. It is used by the release workflow to create GitHub Releases, by the Docker workflow to push images to GHCR, and by Bencher to post PR comments.

Step-by-Step

1. Decide the version number

Follow Semantic Versioning:

Change typeBumpExample
Breaking SQL API or config changeMajor1.0.0 → 2.0.0
New feature, backward-compatibleMinor0.1.0 → 0.2.0
Bug fix, no API changePatch0.2.0 → 0.2.1
Pre-release / release candidateSuffix0.3.0-rc.1

2. Update the version

Four files must have their version bumped together:

# 1. Cargo.toml — the canonical version source for the extension
#    Change:  version = "0.7.0"  →  version = "0.8.0"

# 2. pgtrickle-tui/Cargo.toml — the TUI binary; must always match Cargo.toml
#    Change:  version = "0.7.0"  →  version = "0.8.0"

# 3. META.json — the PGXN package metadata
#    Change both top-level "version" and the nested "provides" version

# 4. CHANGELOG.md
#    Rename ## [Unreleased] → ## [0.8.0] — YYYY-MM-DD
#    Add a new empty ## [Unreleased] section at the top

Important: Cargo.toml (extension) and pgtrickle-tui/Cargo.toml (TUI) must always carry the same version. They are built and released together, and a mismatch causes cargo install --path pgtrickle-tui to report the wrong version. The just check-version-sync script does not currently enforce this, so it must be checked manually.

The extension control file (pg_trickle.control) uses default_version = '@CARGO_VERSION@', which pgrx substitutes automatically at build time — no manual edit needed there.

After editing, verify all version-related files are in sync:

just check-version-sync

3. Commit the version bump

git add Cargo.toml META.json CHANGELOG.md
git commit -m "release: v0.8.0"
git push origin main

4. Wait for CI to pass and verify upgrade completeness

Ensure the CI workflow passes on main with the version bump commit. All unit, integration, E2E, and pgrx tests must be green.

Critical: Before tagging, verify that the upgrade script covers all SQL schema changes:

# Run comprehensive upgrade completeness checks
just check-upgrade-all

# If any check fails (e.g. "ERROR: X new function(s) missing from upgrade script"),
# fix the issue by adding the missing SQL objects to:
#   sql/pg_trickle--<prev>--<new>.sql
#
# Then re-run until all checks pass:
just check-upgrade-all  # Should print "All 15 upgrade step(s) passed completeness checks."

Why this matters: New SQL functions, views, tables, and columns added in any prior release must be carried forward in the upgrade script, even if the current release doesn't change them. The upgrade script is the source of truth for what PostgreSQL applies when users run ALTER EXTENSION pg_trickle UPDATE.

Confirm the local and CI upgrade-E2E defaults were advanced to the new release:

just check-version-sync  # Verifies ci.yml, justfile, and test defaults

5. Create and push the tag

git tag -a v0.2.0 -m "Release v0.2.0"
git push origin v0.2.0

This triggers the Release workflow automatically.

6. Monitor the release

Watch the Actions tab for progress. The release workflow runs these jobs in order:

preflight  ──►  build-release (linux, macos, windows)
                      │
                      ▼
                test-release  ──►  publish-release
                              ──►  publish-docker-arch (linux/amd64 + linux/arm64)
                                         │
                                         ▼
                                   publish-docker (merge manifest + push :latest)

The PGXN workflow (pgxn.yml) runs independently and publishes the source archive to pgxn.org in parallel with the release workflow.

7. Make the GHCR package public (first release only)

When a package is pushed to GHCR for the first time it is private by default. Because this is an open-source project, packages linked to the public repository inherit public visibility — but you must make the package public once to unlock that:

  1. Go to github.com/⟨owner⟩ → Packages → pg_trickle-ext
  2. Click Package settings
  3. Scroll to Danger ZoneChange package visibility → set to Public

After that first change:

  • All future pushes keep the package public automatically
  • Unauthenticated docker pull ghcr.io/grove/pg_trickle-ext:... works
  • Storage and bandwidth are free (GHCR open-source advantage)
  • The package page shows the README, linked repository, license, and description from the OCI labels

8. Verify the release

Once both workflows complete:

  • Check the GitHub Releases page for the new release
  • Verify all three platform archives are attached (.tar.gz for Linux/macOS, .zip for Windows)
  • Verify SHA256SUMS.txt is present
  • Verify the extension image is available at ghcr.io/grove/pg_trickle-ext:<version>
  • Verify the PGXN upload succeeded: pgxn info pg_trickle should show the new version
  • Optionally verify the extension image layout:
docker pull ghcr.io/grove/pg_trickle-ext:<version>
ID=$(docker create ghcr.io/grove/pg_trickle-ext:<version>)
docker cp "$ID:/lib/" /tmp/ext-lib/
docker cp "$ID:/share/" /tmp/ext-share/
docker rm "$ID"
ls -la /tmp/ext-lib/ /tmp/ext-share/extension/

Post-Release Checklist

Complete these steps immediately after a release tag has been pushed and both the Release and PGXN workflows have finished successfully.

  • Create a post-release branch from main (e.g. post-release-<ver>-a)
  • Bump Cargo.toml version to the next development version (e.g. 0.12.00.13.0)
  • Bump pgtrickle-tui/Cargo.toml version to the same next development version — must always match Cargo.toml
  • Bump META.json — both the top-level "version" and the nested "provides" → "pg_trickle" → "version" to match
  • Write plans/PLAN_0_<next>_0.md — initial planning document for the next milestone
  • Delete plans/PLAN_0_<released>_0.md — remove the now-completed plan
  • Wrap roadmap items — in ROADMAP.md, wrap all completed items from the old release with <details> tags to archive them
  • Add ## [Unreleased] stub to CHANGELOG.md above the just-released entry
  • Create sql/pg_trickle--<released>--<next>.sql — empty upgrade script stub for the next migration hop
  • Copy sql/archive/pg_trickle--<released>.sqlsql/archive/pg_trickle--<next>.sql — placeholder archive baseline for the next version
  • Update justfile — advance build-upgrade-image and test-upgrade to defaults to <next>; update the build-hub Docker image tag
  • Update tests/e2e_upgrade_tests.rs — advance all unwrap_or("<released>".into()) fallback strings to <next>
  • Update version numbers in README.md — search for occurrences of the released version (e.g. 0.17.0) and advance them to <next>: CNPG image reference (ghcr.io/grove/pg_trickle-ext:<version>), dbt revision tag, and any other hardcoded version strings. A quick check: grep -n '<released>' README.md
  • Run just check-version-sync — must exit 0 before opening the PR
  • Open a PR against main with the commit title chore: start v<next> development cycle

Preparing for the Next Release (Pre-Work Checklist)

Use this checklist at the start of each new release milestone to ensure the repository is properly set up before development begins. This maps directly to what just check-version-sync verifies.

File / targetActioncheck-version-sync check
Cargo.tomlversion = "<next>"canonical version source
META.jsonboth "version" fields set to <next>PGXN manifest
CHANGELOG.md## [Unreleased] section present(manual hygiene)
sql/pg_trickle--<prev>--<next>.sqlstub file existsupgrade SQL exists
sql/archive/pg_trickle--<next>.sqlplaceholder file exists (copy of <prev>)archive SQL exists
.github/workflows/ci.ymlupgrade matrix and chain end at <next>CI matrix up to date
justfilebuild-upgrade-image and test-upgrade to defaults = <next>justfile defaults
tests/e2e_upgrade_tests.rsall unwrap_or fallbacks = "<next>"e2e fallback strings

Quick-verify with:

just check-version-sync
# Should print: All version references are in sync.

Release Artifacts

Each release produces:

ArtifactDescription
pg_trickle-<ver>-pg18-linux-amd64.tar.gzExtension files for Linux x86_64
pg_trickle-<ver>-pg18-macos-arm64.tar.gzExtension files for macOS Apple Silicon
pg_trickle-<ver>-pg18-windows-amd64.zipExtension files for Windows x64
SHA256SUMS.txtSHA-256 checksums for all archives
ghcr.io/grove/pg_trickle-ext:<ver>CNPG extension image for Image Volumes (amd64 + arm64)

Installing from an archive

tar xzf pg_trickle-<version>-pg18-linux-amd64.tar.gz
cd pg_trickle-<version>-pg18-linux-amd64

sudo cp lib/*.so "$(pg_config --pkglibdir)/"
sudo cp extension/*.control extension/*.sql "$(pg_config --sharedir)/extension/"

Then add to postgresql.conf and restart:

shared_preload_libraries = 'pg_trickle'

See INSTALL.md for full installation details.

Pre-releases

Tags containing -rc, -beta, or -alpha (e.g., v0.3.0-rc.1) are automatically marked as pre-releases on GitHub. Pre-release extension images are tagged but do not update the latest tag.

Hotfix Releases

For urgent fixes on an older release:

# Branch from the tag
git checkout -b hotfix/v0.2.1 v0.2.0

# Apply fix, bump version to 0.2.1
git commit -am "fix: ..."
git push origin hotfix/v0.2.1

# Tag from the branch (CI will still run the release workflow)
git tag -a v0.2.1 -m "Release v0.2.1"
git push origin v0.2.1

Files to Update for Each Release

Every release requires manual updates to the files below. Missing any of them leads to version skew between the code, the docs, and the packages.

FileWhat to changeWhy
Cargo.tomlversion = "x.y.z" fieldThe canonical version source. pgrx reads this at build time and substitutes it into pg_trickle.control via @CARGO_VERSION@. The git tag must match.
META.jsonBoth "version" fields (top-level and inside "provides")The PGXN package manifest. The pgxn.yml workflow uploads this file as part of the source archive; a stale version here means the wrong version appears on pgxn.org.
CHANGELOG.mdRename ## [Unreleased]## [x.y.z] — YYYY-MM-DD; add a new empty ## [Unreleased] at the topKeeps the public changelog accurate and gives downstream users a dated record of changes.
ROADMAP.mdUpdate the preamble's latest-release/current-milestone lines; mark the released milestone done; advance the "We are here" pointer to the next milestoneKeeps the forward-looking plan aligned with reality. Leaves no confusion about what just shipped versus what is next.
README.mdUpdate test-count line (~N unit tests + M E2E tests) if test counts changed significantlyThe README is the first thing users read; stale numbers erode trust.
INSTALL.mdUpdate any version numbers in install commands or example URLsUsers copy-paste installation commands; stale versions cause failures.
docs/UPGRADING.mdAdd the new version-specific migration notes and extend the supported upgrade-path tableDocuments exactly what ALTER EXTENSION ... UPDATE will do and which chains are supported.
sql/pg_trickle--<old>--<new>.sqlAdd or update the hand-authored upgrade script for every SQL-surface change (new objects, changed signatures, changed defaults, view changes). Also carry forward all functions/views/tables added in previous releases — the upgrade script is cumulative.ALTER EXTENSION ... UPDATE only applies what is explicitly scripted; function defaults and signatures stored in pg_proc do not update themselves. Omitting a function that existed in <old> but is expected in <new> will break user upgrades.
sql/archive/pg_trickle--<new>.sqlRegenerate and commit the full-install SQL baseline for the new version. This file was created as a placeholder copy of <prev> at the start of the development cycle — it must be replaced with the actual generated SQL before tagging. Run cargo pgrx schema (or the equivalent just target) to produce the final schema, then overwrite the placeholder.Future upgrade-completeness checks and upgrade E2E tests need an exact baseline for the released version. A stale placeholder from the start of the cycle will cause spurious failures.
.github/workflows/ci.yml, justfile, tests/build_e2e_upgrade_image.sh, tests/Dockerfile.e2e-upgradeAdvance the upgrade-check chain and default upgrade-E2E target version to the new releasePrevents release automation and local upgrade validation from getting stuck on the previous version after a new migration hop is added.
pg_trickle.controlNo manual edit neededdefault_version is set to '@CARGO_VERSION@' and pgrx substitutes it at build time. Verify the substitution in the built artifact.Ensures the SQL CREATE EXTENSION command installs the right version.

CRITICAL: After updating sql/pg_trickle--<old>--<new>.sql, always run just check-upgrade-all to verify that the upgrade script is complete. This checks not just the immediate hop to the new version, but the entire upgrade chain from v0.1.3 onwards. If the check fails (e.g. "ERROR: 3 new function(s) missing"), it means the upgrade script is missing one or more SQL objects that users will expect to have after upgrading. Fix all failures before tagging.

Checklist summary

[ ] Cargo.toml — version bumped
[ ] META.json — both "version" fields updated to match
[ ] CHANGELOG.md — [Unreleased] renamed to [x.y.z] with date; new empty [Unreleased] added
[ ] ROADMAP.md — preamble updated; released milestone marked done
[ ] README.md — test counts current (if materially changed)
[ ] INSTALL.md — version references current
[ ] docs/UPGRADING.md — latest migration notes and supported chains added
[ ] sql/pg_trickle--<old>--<new>.sql — covers every SQL-surface change AND carries forward all previous release functions
[ ] sql/archive/pg_trickle--<new>.sql — regenerated from final schema and committed (replaces the dev-cycle placeholder)
[ ] just check-upgrade-all — all upgrade steps pass completeness checks (not just the one-step hop)
[ ] Upgrade automation defaults — CI/local upgrade checks and E2E target the new version
[ ] just check-version-sync — all version references in sync
[ ] All CI checks on main have passed (verify the last run on the version-bump commit succeeded)
[ ] git tag matches Cargo.toml version

Troubleshooting

Release workflow failed

Go to the Actions tab and identify which job failed. Then follow the appropriate recovery path below.

Option A: Re-run (transient failure)

If the failure is transient — network timeout, registry hiccup, runner issue — you can re-run without changing anything:

  1. Open the failed workflow run in the Actions tab
  2. Click Re-run all jobs (or re-run just the failed job)

This works because the v* tag still points to the same commit, and the workflow uses cancel-in-progress: false so a re-run won't be cancelled.

Option B: Fix code and re-tag

If the failure is a real build or code issue:

# 1. Delete the remote tag
git push origin :refs/tags/v0.2.0

# 2. Delete the local tag
git tag -d v0.2.0

# 3. Fix the issue, commit, and push
git add <files>
git commit -m "fix: ..."
git push origin main

# 4. Re-tag on the new commit and push
git tag -a v0.2.0 -m "Release v0.2.0"
git push origin v0.2.0

This triggers a fresh release workflow run.

Option C: Clean up a partial GitHub Release

If the workflow created a draft or partial Release before failing:

  1. Go to Releases in the repository
  2. Delete the broken release (this does not delete the tag)
  3. Then follow Option A or Option B above

Upgrade script completeness check failed

If just check-upgrade-all reports errors like "ERROR: X new function(s) missing from upgrade script", it means the upgrade SQL script is incomplete:

# 1. Look at the error — it tells you exactly what's missing
just check-upgrade-all  # e.g. "ERROR: 3 new function(s) missing from upgrade script:
                        #        - pgtrickle.\"explain_refresh_mode\"
                        #        - pgtrickle.\"fuse_status\"
                        #        - pgtrickle.\"reset_fuse\""

# 2. Find where those objects are defined in the previous release
#    (they should already exist in sql/archive/pg_trickle--<prev>.sql)
grep -n "CREATE.*FUNCTION.*explain_refresh_mode" sql/archive/pg_trickle--*.sql

# 3. Copy the function definitions (CREATE OR REPLACE FUNCTION) to the
#    upgrade script you're fixing. They should go into:
#    sql/pg_trickle--<old>--<new>.sql
#    
#    Typically, carry-forward functions are grouped in their own section
#    at the top of the upgrade script with a comment explaining they're
#    from a prior release.

# 4. Re-run the check to verify it passes
just check-upgrade-all

Why this happens: When a new release (e.g. v0.11.0) adds SQL functions, those functions must be explicitly included in all subsequent upgrade scripts. The upgrade script is the ground truth — PostgreSQL only applies what is listed in the .sql file. If you skip a function that users expect, their upgraded extension will be missing that object.

Common failure causes

SymptomCauseFix
Version mismatch errorCargo.toml version doesn't match the git tagRun just check-version-sync, fix any skew, commit, delete tag, re-tag (Option B)
Build failureCompilation error in release profileFix on main, re-tag (Option B)
Docker push failedMissing permissionsVerify packages: write is in the workflow and GITHUB_TOKEN has GHCR access, then re-run (Option A)
Smoke test failedExtension doesn't load in PostgreSQLFix the issue, re-tag (Option B)
PGXN upload failedMissing PGXN_USERNAME / PGXN_PASSWORD secrets, or META.json version not updatedAdd the secrets in repository settings; verify META.json version matches the tag; re-run the pgxn.yml workflow from the Actions tab
just check-upgrade-all reports missing functions/viewsUpgrade script is incomplete — new objects from prior releases not carried forwardSee "Upgrade script completeness check failed" above for recovery steps
Rate limitedGitHub API or GHCR throttlingWait a few minutes, then re-run (Option A)

Yanking a release

If a release has a critical issue:

  1. Mark it as pre-release on the GitHub Releases page (uncheck "Set as the latest release")
  2. Add a warning to the release notes
  3. Publish a patch release with the fix