The release workflow

molt splits releasing into three verbs on two clocks: add records intent as you work, version consumes accumulated intent into concrete releases, and publish ships them.

If The changeset workflow explains why intent-based versioning works, this page explains how to run it day to day -- as a solo maintainer and as a team on a pull-request flow.

Three verbs

VerbWhenWhat it does
molt addContinuously, once per meaningful changeWrites a changeset: affected packages, bump types, a summary.
molt versionIn a batch, whenever you decide to releaseConsumes every pending changeset: bumps versions, propagates to dependents, writes changelogs, updates the lockfile, deletes the changesets.
molt publishRight after versioningBuilds and uploads exactly the packages whose versions changed, and tags them.

Two supporting verbs round it out: molt status shows what a release would contain, and any mutating command accepts --dry-run to print its plan without touching disk.

The two clocks

The idea that makes everything click is that adding intent and consuming intent run on two independent clocks.

  • The development clock ticks once per meaningful change, driven by whoever wrote it. Ten pull requests over two weeks drop ten changesets into .changeset/.
  • The release clock ticks whenever you decide to release. molt version drains the whole .changeset/ buffer at once and computes the correct version for every affected package.

Development clock

Ticks once per change, by whoever wrote it.

  • PR #41molt addacme-coreminor
  • PR #42molt addacme-clipatch
  • PR #43molt addacme-corepatch

.changeset/

The buffer. Nothing is released while it fills.

  • slow-lions-cough.md
  • brave-mugs-sing.md
  • tidy-eels-return.md

Release clock

Ticks when you decide. molt version drains the whole buffer at once.

  • acme-core1.2.01.3.0two changesets, one release: the minor wins
  • acme-cli0.5.00.5.1
Three changesets, two releases. Nobody picked a version number when they opened a pull request, because the number depends on everything shipping in the release.

Nobody has to pick a final version number when they open a pull request, because the number depends on everything that ships in the release -- which is not known until release time. A package with a pending minor and a pending patch moves once, by the higher bump. Three changesets do not mean three releases; they mean one release that reflects the most significant change, with all three summaries in the changelog.

The team flow: changesets in pull requests

On a team, the changeset is a reviewable artifact that travels with the code.

  1. A contributor opens a PR with their change and a changeset committed alongside it (molt add). A reviewer can see at a glance that this PR intends, say, a minor bump to acme-core.
  2. CI gates the PR with molt status. It fails when a versionable package changed but no changeset was added -- a prompt to run molt add, or molt add --empty if the change genuinely needs no release.
  3. Changesets accumulate on the default branch as PRs merge. Nothing is released yet; intent is just piling up in .changeset/.
  4. A release job runs molt version and opens a "Version Packages" pull request containing the version bumps, changelog updates, and lockfile changes -- with the changesets consumed. This PR is the human-readable preview of the next release.
  5. Merging that PR triggers molt publish, which builds and uploads the changed packages and pushes their tags.
Steps 4 and 5 in GitHub Actions

You do not script steps 4 and 5. The GitHub Action runs both phases on every push to your base branch: it opens and updates the "Version Packages" pull request while changesets are pending, and publishes to PyPI over OIDC once you merge it.

- uses: giancarlosisasi/molt-action@v1
  with:
    publish: molt publish
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

That guide draws the whole pull-request flow step by step. The same shape works on any forge, since the forge is a seam.

When to use --dry-run

Every mutating command -- add, version, publish, build, git-tag -- can build its plan and print it without executing. Reach for --dry-run when you want to look before you leap:

  • Before a release, molt version --dry-run shows every version bump and file it would write. molt status answers the same question and is the read-only, CI-friendly form.
  • Before publishing, molt publish --dry-run shows exactly which packages and versions would be uploaded, and in what order.
  • In CI or a script, pass --output json to get the plan as a machine-readable object you can gate on.

Because the plan is the same value molt would otherwise execute, a dry run is a faithful preview, not an approximation.

A note on exit codes

molt's exit codes are designed for CI, and a couple are worth internalizing:

  • molt version exits 1 when there are no pending changesets. There is nothing to release, and that is treated as a failure so a misconfigured pipeline does not silently "succeed".
  • molt status exits 1 when a package changed but no changeset covers it (the CI gate above), and 0 when there is genuinely nothing to release.
  • molt publish exits 0 when there is nothing new to upload.

The full contract is in the CLI overview.

Where to go next