RELEASING
Notes for hledger release managers and maintainers.
Goals
2026
- ?
2025
- Make releasing easier
- Improve automation
- Improve process docs
- Track releasing and release engineering time explicitly, per release
2024
- Make releasing easier
2023
- Make releasing eas
yier
2022
- Update/consolidate release process docs,
- Establish routine
monthlyrelease cadence, - Make releasing easy
hledger release types
hledger major releases happen each quarter, normally at the start of the third month (see past releases). Bugfix releases follow those when needed, usually soon after. Preview/nightly releases may happen any time.
| Major release A.B | Bugfix release A.B.C | Preview/Nightly release A.B.99 | |
|---|---|---|---|
| Contains: | New features, breaking changes | Only bug fixes | Early snapshot of the next major release |
| When: | Start of third month in quarter: Mar, Jun, Sep, Dec | When needed | Occasionally, as needed |
| Deliverables: | |||
| Changelogs | ✓ | ✓ | ✓ |
| Github release | ✓ | ✓ | ✓ |
| Binaries | ✓ | ✓ | ✓ |
| Hackage release | ✓ | ✓ | |
| Install page | ✓ | ✓ | |
| Regression bounties | ✓ | ✓ | |
| Release notes | ✓ | ✓ | |
| Manuals snapshot | ✓ | ||
| Announcements | ✓ |
hledger release artifacts / value chain
Higher things depend on lower things. Release readiness and the release process go from the bottom of this diagram to the top.
Release script
Last updated: 2026-08
Same steps as Release script (2026H1), reordered and annotated with lessons from the 1.52.2 release.
For extra how-to’s not covered here, see the “How to..” and “Tips” sections below - but not the numbered
waypoint list in the Release checklist section itself, which predates this script (last updated 2025-11)
and may conflict with it on step order/commands.
Key: main: = in hledger repo main branch, rel: = in hledger repo release branch, site: = in site repo,
(CONDITION) … = when CONDITION is true, CMD = suggested relevant command.
Steps marked ⚠ push/upload/publish/announce to somewhere shared and are hard or impossible to undo -
always get explicit go-ahead for that specific step, even mid-release, even if earlier steps were approved.
Steps without ⚠ are safe to just do once they’re clearly next.
The actual commands referred to above live in Justfile, Shake.hs, tools/.
This file (doc/.RELEASING.md) is the maintainer’s working copy of doc/RELEASING.md, edited live during
a release to avoid interfering with branch switching; it may be temporarily ahead of doc/RELEASING.md,
which should be updated from it after the release.
-
before any step: confirm the current branch (
git branch --show-current) matches that step’smain:/rel:/site:label. The branch can change between your checks (e.g. the maintainer switching branches outside your tool calls), so re-check rather than trusting an earlier check. -
main: finish fixes/features/docs/issues/prs
-
main: begin/fix release builds:
just ghbin oldest -
main: update general flags help: build hledger, copy general flags help from
stack exec -- hledger -hto common.m4 -
main: update command docs and manuals ?
just manuals -
(major release) main: update website manuals:
just manuals-site -
(major release) main: update website scripts/redirects: update
site/Makefile,site/js/site.js,site/hledger.org.caddy -
main: create/update release branch:
just relbranch VER(for 1.99.x preview releases, create branch manually)- if a GHC version the release branch needs isn’t installed locally, avoid installing it if possible - save a copy
of main’s
stack.yaml(e.g. asstackmain.yaml), then use-w stackmain.yamlwith stack commands, or temporarily replace the release branch’sstack.yamlfor tools like Shake that don’t take-w. Always restore the realstack.yaml(git checkout -- stack.yaml) afterward, and checkgit statusfor stragglers before committing/pushing.
- if a GHC version the release branch needs isn’t installed locally, avoid installing it if possible - save a copy
of main’s
-
(minor release) rel: cherry-pick new changes from main
-
rel: update command docs and manuals:
just manuals -
rel: update changelogs:
just changelogs; edit by hand;just changelogs-finalise -
rel: update relnotes:
just relnotes; edit (add summary); commit- it’s normal for some packages to have zero changes in a bugfix release -
just relnotesemits a one-line “Uses PKG X.Y.Z” for them; not a problem for downstream packagers.
- it’s normal for some packages to have zero changes in a bugfix release -
-
rel: update announcements: edit
doc/ANNOUNCE -
rel: make release builds:
just ghbin -
rel: update install docs: edit
doc/ghrelnotes,doc/ghtestbinnotes.md,site/src/install.md- do this on the release branch, before the cherry-pick below, not on main (main’s copies of ghrelnotes/ghtestbinnotes.md describe the next preview line and are unrelated to the release branch’s version). -
main: cherry-pick changelogs, relnotes, announcement, other relevant updates from relbranch
jjui -r :: -
rel: make release tags: (once binaries are all built)
just reltags- safe to re-run/move if the release branch gets more commits before tags are pushed. -
(non-preview release) rel: publish on hackage:
just hackageupload⚠ (no unpublish - confirm before running the actual upload, distinct from the earlier reversible build/upload steps) -
push to github: push site repo, push VER-branch,
just reltags-push VER, push main ⚠ -
publish on github: manually make new github release (latest or prerelease) from VER tag;
just ghrel-notes;just ghbin-download ghrel-upload(re-upload via--clobberis safe/reversible; drafting the release is fine, but making it public/published is ⚠ - confirm first)- create it as a non-draft release, named after the tag (as usual): a draft has no stable url, which
just ghrel-notesandghrel-uploadneed. So the release goes public before its notes/binaries are attached - keep this window short. ghbin-downloadtakes the latest run of each binaries workflow; check those runs are the ones built from the tagged commit (just _ghrun-id binaries-linux-x64etc), especially if anything was pushed to thebinariesbranch since.- a good final check before uploading: unpack the archive for your own platform and run
./hledger --versionetc - it should showVER-gHASHmatching the release tag’s commit. (Use--conf=/dev/nullif your personal config uses newer syntax than the release understands.)
- create it as a non-draft release, named after the tag (as usual): a draft has no stable url, which
-
(major release) main: activate website scripts/redirects:
just site-restart -
(major release) main: update dev version:
just devver -
main: update manuals:
just manuals -
main: update changelogs:
just changelogs; edit -
announce to matrix, irc, mail list, mastodon, forum, pta.o ⚠
-
(if this release fixes a previously-embargoed security issue)
- keep the GHSA draft private until the release is out and installable
- publish the advisory once the fix has had a little time to propagate; don’t gate on CVE grant, that’s a separate async process that can take much longer
- cross-link: relnotes/CHANGES entry → advisory, advisory → release tag/binaries
- if an issue/PR had to be redacted when the bug was first (prematurely) disclosed, restore its original content once the advisory is published, and link it to the advisory. A published GHSA is already public and indexed (repo Security tab, github.com/advisories, OSV, scanners) regardless of what links to it, so redacting only helps for as long as the PR/issue contains materially more detail than the advisory does.
Release script (2026H1)
Short version, based on the checklist below.
Last updated: 2026-06
Key: main: = in hledger repo main branch, rel: = hledger repo release branch, site: = site repo.
- main: finish fixes/features/docs/issues/prs
- main: begin/fix release builds:
j ghbin oldest - main: update general flags help: build hledger, copy general flags help from
stack exec -- hledger -hto common.m4 - main: update command docs and manuals ?
j manuals - (major release) main: update website manuals:
j manuals-site - (major release) main: update website scripts/redirects: update
site/Makefile,site/js/site.js,site/hledger.org.caddy - main: create/update release branch:
j relbranch VER(for 1.99.x preview releases, create branch manually) - (minor release) rel: cherry-pick new changes from main
- rel: update command docs and manuals:
j manuals - rel: update changelogs:
j changelogs; edit;j changelogs-finalise - rel: update relnotes:
j relnotes; edit (add summary); commit - rel: update announcements: edit
doc/ANNOUNCE - rel: make release builds:
j ghbin - rel: update install docs: edit
doc/ghrelnotes,doc/ghtestbinnotes.md,site/src/install.md - main: cherry-pick changelogs, relnotes, announcement, other relevant updates from relbranch
jjui -r :: - rel: make release tags: (once binaries are all built)
j reltags - (non-preview release) rel: publish on hackage:
j hackageupload - push to github: push site repo, push VER-branch,
j reltags-push VER, push main - publish on github: manually make new github release (latest or prerelease) from VER tag;
j ghrel-notes;j ghbin-download ghrel-upload - (major release) main: activate website scripts/redirects:
j site-restart - (major release) main: update dev version:
j devver - main: update manuals:
j manuals - main: update changelogs:
j changelogs; edit - announce to matrix, irc, mail list, mastodon, forum, pta.o
Release checklist
This is a detailed expansion of the release artifacts / value chain diagram above, listing waypoints, required artifacts, and related commands. Last updated: 2025-11
-
0. general
- when browser (Safari) refuses to show new content, use another
-
1. product
- blocking defects resolved
- desired improvements landed and stabilised
- building and passing tests with current ghcs, deps, and stackage snapshots
- building and passing tests on all platforms / with all ghc versions
just ghbin(or push to githubbinaries[-*]branch)just oldest(or push to githuboldestbranch)
-
2. product docs and metadata
- release branch
- version strings (in **/.version, */.version.m4, */package.yaml)
- cabal files x 4 (hledger*/hledger*.cabal)
just relbranch VER,just cabalfilestest - options help texts up to date (in CliOptions.hs, UIOptions.hs, WebOptions.hs)
stack build./Shake cmddocs -c - embedded manuals x 3
- generaloptions macro (in doc/common.m4)
- tool specific options in manuals (hledger*/hledger*.m4.md > # Options)
- man page dates (*/.date.m4)
- man (hledger*/hledger*.1)
- info (hledger*/hledger*.info)
- text (hledger*/hledger*.txt)
./Shake mandates./Shake manuals -c
- embedded tldr pages synced with upstream (doc/tldr/*)
just tldr-diff - shell completions (hledger/shell-completion/hledger-completion.bash)
just completions, commit any changes - changelogs x 5 (*/CHANGES.md)
just changelogs [-c]
group the new/unreleased entries by topic, not by change type (Fixes/Features/Improvements) - choose topics appropriate to this release’s actual changes, using the previous major release’s topic headings as a starting point (not a fixed list); keep## Breaking changesand the trailing## Docs/## Examples/## Scripts/addons/## APIsections as-is
add notable changes from site, finance repos to project changelog (major release only)
add issue links withmd-issue-refs, uniquify
add author github nicksjust changelogs-finalise
-
3. release docs and artifacts
- draft binaries building started
just ghbin - hledger.org html manuals x 3 (site/src/MAJORVER/*.md) (major release only)
just site-manuals-snapshot MAJORVERto create/update
updatesite/Makefile,site/js/site.js,site/hledger.org.caddy - release notes @ hledger.org (doc/relnotes.md)
just relnotes(XXX minor release: moves previous release’s summary, adds whitespace)
add summary (major release only)
add issue links with md-issue-refs
bump changelog links at the top
commit - github binary install docs (doc/ghrelnotes.md) up to date and pre-tested
- Install page (site/src/install.md) up to date and pre-tested
- draft announcement for chat, mail list, mastodon etc (doc/ANNOUNCE)
- release tags
just reltags - release binaries built from tag
just ghbin, wait for all to succeed - Install page (site/src/install.md) –version examples match release binaries
- draft binaries building started
-
4. published
- relevant release branch work cherry-picked to main branch
changelogs, relnotes, announcements - all packages uploaded correctly to hackage
just hackageupload - main branch pushed to github
- new manuals published and rendering/redirecting correctly
- site repo pushed to github
- main and site repos auto-pulled to hledger.org, site rebuilt
hledgerorgsh grep release.= /opt/hledger/site/out/js/site.js - https://www.hledger.org/js/site.js showing latest version
curl -s https://hledger.org/js/site.js | grep release.=
purge cache at https://dash.cloudflare.com/f629035917dd3b99b1e37ae20c15ff09/hledger.org/caching/configuration (major release only) - default manual urls redirecting to latest version (major release only)
hledgerorgsh sh -c 'systemctl stop caddy; systemctl start caddy'curl -sI https://hledger.org/hledger.html | grep location
- release branch pushed to github
- release tags pushed to github
just reltags-push VER - github draft release with release binaries attached
https://github.com/plaintextaccounting/hledger/releases/new (XXX safari may not show new tag, may need brave)just ghrel-notes(in release branch)just ghbin-downloadjust ghrel-bin-upload VER - github release published
decide if release should be immutable (artifacts attached, all correct ?) then adjust repo settings
review, publish - github nightly release updated (XXX nightly release deleted, needs reviving)
in main, update changes link in doc/ghnightlynotes.mdjust nightlyrel-notes - install instructions tested and working
- stack
- cabal
- source checkout
- github release > How to install, each platform
- announced
- mail list(s) [email protected] for major (+ [email protected] for supermajor)
- matrix
- irc
- mastodon
- pta forum
- relevant release branch work cherry-picked to main branch
-
5. cleanup and support
- review/polish/sync changelogs & relnotes
- new version, man dates, dev tag in main (major version only)
j devtag-push - RELEASING.md checklist/notes updated
- monitor/support/handle issues: issue tracker, matrix, irc, mail list, forum, reddit
Some more good things to do after a release:
- hledger.org site/doc updates
- hledger_finance repo updates
- plaintextaccounting site updates (eg project stats)
How to..
More procedure notes.
Check release readiness
- Any blocking open issues ? https://bugs.hledger.org
- Any blocking open PRs ? https://prs.hledger.org
- Any blocking items on https://hledger.org/ROADMAP.html ?
- Any blocking items in personal notes & backlogs ?
Check tools are up to date
- Check for consistent stackage snapshot(s) and extra deps used in stack.yaml, Shake.hs, hledger-install.sh, bin scripts, tools scripts
- Shake binary is up to date
./Shake.hs hpack --versionmatches the one instack --version
Run local tests
just testjust doctestjust haddocktest
Run CI tests
- push to a PR, wait for green
- or push to
cibranch, wait for green at https://ci.hledger.org - or
just push(pushes toci, then tomain)
Run release branch tests
just teststack exec -- hledger --version, check version, hash, release date, no ‘+’stack exec -- hledger help | tail, check version, month matches release
Update the Install page
In site repo:
- update
install.md- query-replace OLD -> NEW in
- “current hledger release”
- CI binaries badges/links, including linux-static-arm32v7 if built
- “building from source”
- stack install command
- cabal install command
- query-replace OLD-brightgreen -> OLD-red
- only after release binaries are built (preferably after release is published): update –version outputs (version, hash, date, but not platform)
- final output line from
hledger test(run local build and in terminal for normal speed) - Total count from
make functest - preview
- commit:
install: NEW
- query-replace OLD -> NEW in
Update hledger in stackage
- monitor packaging status in lts and nightly: https://www.stackage.org/package/hledger
- update https://github.com/fpco/stackage/blob/main/build-constraints.yaml as needed
Update RELEASING.png
- edit RELEASING.canvas in obsidian
- CMD-p > Export as image, don’t show logo
- commit
Tips
-
Release, or practice releasing, often to improve the process.
-
Use and continually update RELEASING.md. Document procedures and gotchas to save time and enable automation in future.
-
Also the diagram (RELEASING.canvas, made with Obsidian).
-
But don’t document prematurely or in too much detail.
-
Make things a little better each time through: simpler, more reliable, better documented, more automated, easier, faster, cheaper, higher quality.
-
Optionally save this file as RELEASING2.md and update notes there until after release, if it’s interfering with git branch switching.
-
Use and update scripts, in
Justfile,Shake.hs,tools/etc. -
Do all releases from a release branch.
-
Update dev changelogs frequently in main. Finalise changelogs in the release branch. Merge back to main after release. (Related older doc: CHANGELOGS)
-
All release binaries should be built from the release-tagged commit. The binaries’ –version output should match the release tag and release date.
-
Try to do only full releases including all four main hledger packages; partial releases add complexity.
-
Try to avoid pre-announcing a hard release date. It will always take more time than you think, if you go late you might miss your intended date in many timezones, and there’s no point adding unnecessary pressure.
-
The biggest potential time sinks are:
- reviewing/relearning the process/docs/infrastructure
- updating/improving the process/docs/infrastructure
- preparing changelogs
- building binaries for all platforms
- troubleshooting github workflow issues
- followup work due to release mistakes, bugs in new features, or regressions
-
Hard/risky/intensive tasks should happen without time pressure; during the final countdown, things should be easy.
Release manager activities
These have complex interdependencies and sequencing constraints. Chunk, separate, routinise, document and automate them as far as possible.
| Software | selecting changes, packages, release dates; coordinating contributions; ensuring release readiness |
| Branch Management | coordinating main and release branch, local and remote repos, CI branches |
| Version Bumping | choosing and applying new version numbers and related things like tags, github releases, urls, ghc and dep versions, stackage resolvers, everywhere needed |
| Docs | command help, manuals, changelogs, release notes, github release notes, install page, install scripts, announcements, process docs |
| Testing | local testing, CI testing, extra release-specific testing |
| Artifacts | generating binaries, zip files, github releases etc. |
| Publishing | uploading, pushing, making visible, finalising |
| Announcing | various announcement stages and channels |
Glossary
Some standard terminology, useful when precision is needed, eg in release scripts.
General
release
A snapshot of the software and related artifacts like executable binaries, which is named, tagged, documented, announced, and usually picked up by packaging systems on various platforms.
version control system, VCS
A tool used for storing and sharing and viewing the history and different lines of development of a software project, or other set of files. hledger uses Git.
repository, repo
A set of files being stored and managed by a VCS. Often published on a repository hosting service, such as Github.
working copy, clone
A local copy of a repository’s files. Typically each developer has one or more of these, and can share changes easily with the official public repository.
branch
Some VCS’s, including Git, can store multiple branching lines of development within one repository. A working copy can be quickly switched to a different branch to show its content.
main
The main branch in a repo, usually named main or master. Pull requests are usually relative to this.
pull request, PR
A request to merge a development branch with main, and any related discussion. On Github, these are kept alongside issues in the issue tracker.
continuous integration, CI
Automated actions that run when new code is pushed to a shared repo, such as running tests or producing binaries. On Github this is called Github Actions and action scripts are called workflows.
release engineering
https://en.wikipedia.org/wiki/Release_engineering
hledger-specific
package
A releasable unit of Haskell software. hledger has several core packages usually released together: hledger-lib, hledger, hledger-ui, hledger-web.
hledger version number
A 2-4 part dotted number naming a hledger release or hledger package version: MA.JOR[.MINOR[.FIXUP]] or MA.JOR.99[.PREVIEW] where 99 means “unreleased (MAJOR+1)”. See examples below.
hledger version string
A line of text describing a hledger binary, shown by --version. It contains program name, version number, commit hash and date, machine architecture etc. Eg: hledger 1.24.1-g7799d526b-20211210, mac-x86_64
Full release
A release of all four core hledger packages (hledger-lib, hledger, hledger-ui, hledger-web). Major and preview releases are always full releases.
Partial release
A release of just some of the hledger packages. Bugfix and fixup releases are sometimes partial.
Single-version release
A release where all packages have the same version. Major and preview releases are always single-version.
Mixed-version release
A release where the packages have different versions, because of a previous partial release. Bugfix and fixup releases are sometimes mixed-version.
changelog
A CHANGES.md file listing the release history and the changes in each release. There is one for each hledger package and one for the hledger project as a whole.
release notes
The Release Notes page on the hledger website: the combined release history of the core hledger packages, showing user visible changes only.
Releases and builds
Major release
Major releases include new features and incompatible API changes, and normally happen at the start of each quarter’s third month (3/1, 6/1, 9/1, 12/1). Example version number: 1.25
Bugfix release
Bugfix releases include only bug fixes, without API changes. These happen when needed, to fix significant bugs in the previous major release. Example version number: 1.25.2 (“second bugfix release for 1.25”)
Fixup release
Fixup releases fix packaging errors, with no changes to the hledger software. Example version number: 1.25.0.1 or 1.25.2.1 (“first fixup release for 1.25 / 1.25.2”).
These should rare; we basically never do these.
Preview release
A preview of the upcoming major release for testers/early adopters, and a test of the release process, published on Github. Not a formal hledger release, eg not published on Hackage, usually not packaged, no bugfix releases, no regression bounties, not shown in release notes. These typically appear in the quarter’s first and second month if needed. Example version number: 1.25.99.1 (“preview 1 of 1.26”)
CI binaries
Temporary downloadable binaries produced by a run of the linux/mac/windows workflows in the hledger repo. This may happen periodically, eg weekly. Downloading requires a Github login.
Dev build
A local developer build of unreleased code. This is typically in main or a development/PR branch. Example version number: 1.25.99 (“unreleased 1.26-dev”)
Repos and branches
hledger repo
The hledger git repository, containing the hledger software, reference manuals, and developer docs. https://github.com/plaintextaccounting/hledger
site repo
The hledger_website git repository, containing most of the hledger website which appears at https://hledger.org. Usually checked out under the hledger repo as site/. https://github.com/simonmichael/hledger_website
finance repo
The hledger_finance git repository, containing the hledger project’s financial ledger. Usually checked out under the hledger repo as finance/. https://github.com/simonmichael/hledger_finance
main
The branch named main in the hledger repo; the main line of hledger development. Pull requests are usually relative to this.
release branch
Branches named MA.JOR-branch in the hledger repo, eg 1.25-branch. Releases and release previews are always made from a release branch.
