Skip to content
Latchkey

Release workflow (tox-dev/platformdirs)

The Release workflow from tox-dev/platformdirs, explained and optimized by Latchkey.

A

CI health: A - excellent

Point runs-on at Latchkey and get job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: tox-dev/platformdirs.github/workflows/release.yamlLicense MITView source

What it does

This is the Release workflow from the tox-dev/platformdirs repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: Release
on:
  workflow_dispatch:
    inputs:
      release:
        description: "Release (semver bump)?"
        required: true
        default: "no"
        type: choice
        options: ["no", patch, minor, major]
  push:
    branches: ["main"]
  pull_request:
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}${{ github.event.inputs.release && github.event.inputs.release != 'no' && '-release' || '' }}
  cancel-in-progress: ${{ github.event.inputs.release == 'no' || github.event.inputs.release == null }}
permissions:
  contents: read
env:
  dists-artifact-name: python-package-distributions
jobs:
  build:
    name: build
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      pull-requests: read
    outputs:
      version: ${{ steps.v.outputs.version }}
      changelog: ${{ steps.v.outputs.changelog }}
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          fetch-depth: 0
          persist-credentials: false
      - name: Setup uv
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true
          cache-dependency-glob: "tasks/changelog.py"
      - name: Generate changelog
        id: v
        run: uv run tasks/changelog.py "$RELEASE_TYPE" "$EVENT_NUMBER" "$BASE_SHA"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          RELEASE_TYPE: ${{ github.event.inputs.release == 'no' || github.event.inputs.release == null && 'patch' || github.event.inputs.release }}
          EVENT_NUMBER: ${{ github.event.number }}
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
      - name: Create temporary tag for hatch-vcs
        if: github.event.inputs.release != 'no' && github.event.inputs.release != null
        run: git tag "${STEPS_V_OUTPUTS_VERSION}"
        env:
          STEPS_V_OUTPUTS_VERSION: ${{ steps.v.outputs.version }}
      - name: Build package
        run: uv build --python 3.14 --python-preference only-managed --sdist --wheel . --out-dir dist
      - name: Store the distribution packages
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: ${{ env.dists-artifact-name }}
          path: dist/*
  release:
    name: release
    needs: [build]
    if: github.event.inputs.release != 'no' && github.event.inputs.release != null && github.ref == 'refs/heads/main'
    runs-on: ubuntu-24.04
    environment:
      name: release
      url: https://pypi.org/project/platformdirs/${{ needs.build.outputs.version }}
    permissions:
      id-token: write
      contents: write
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          token: ${{ secrets.RELEASE_TOKEN }} # zizmor: ignore[artipacked]
      - name: Commit changelog and tag
        env:
          CHANGELOG: ${{ needs.build.outputs.changelog }}
          VERSION: ${{ needs.build.outputs.version }}
        run: |
          if git ls-remote --tags origin "refs/tags/$VERSION" | grep -q .; then
            echo "Tag $VERSION already exists, skipping changelog commit"
            exit 0
          fi
          git config user.name "${GITHUB_ACTOR}"
          git config user.email "${GITHUB_ACTOR_ID}+${GITHUB_ACTOR}@users.noreply.github.com"
          header=" $VERSION ($(date -u +%Y-%m-%d))"
          separator=$(printf '%0.s*' $(seq 1 $((${#header} + 1))))
          {
            head -4 docs/changelog.rst
            printf '%s\n%s\n%s\n\n%s\n\n' "$separator" "$header" "$separator" "$CHANGELOG"
            tail -n +5 docs/changelog.rst
          } > docs/changelog.tmp
          mv docs/changelog.tmp docs/changelog.rst
          git add docs/changelog.rst
          git commit -m "Release $VERSION"
          git tag "$VERSION"
          git push origin "$VERSION"
          git push origin main
      - name: Download all the dists
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
        with:
          name: ${{ env.dists-artifact-name }}
          path: dist/
      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
        with:
          skip-existing: true
      - name: Create GitHub release
        env:
          GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
          VERSION: ${{ needs.build.outputs.version }}
        run: |
          if gh release view "$VERSION" > /dev/null 2>&1; then
            echo "Release $VERSION already exists, skipping"
          else
            gh release create "$VERSION" --title="$VERSION" --verify-tag --generate-notes
          fi

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

name: Release
on:
  workflow_dispatch:
    inputs:
      release:
        description: "Release (semver bump)?"
        required: true
        default: "no"
        type: choice
        options: ["no", patch, minor, major]
  push:
    branches: ["main"]
  pull_request:
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}${{ github.event.inputs.release && github.event.inputs.release != 'no' && '-release' || '' }}
  cancel-in-progress: ${{ github.event.inputs.release == 'no' || github.event.inputs.release == null }}
permissions:
  contents: read
env:
  dists-artifact-name: python-package-distributions
jobs:
  build:
    timeout-minutes: 30
    name: build
    runs-on: latchkey-small
    permissions:
      contents: read
      pull-requests: read
    outputs:
      version: ${{ steps.v.outputs.version }}
      changelog: ${{ steps.v.outputs.changelog }}
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          fetch-depth: 0
          persist-credentials: false
      - name: Setup uv
        uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true
          cache-dependency-glob: "tasks/changelog.py"
      - name: Generate changelog
        id: v
        run: uv run tasks/changelog.py "$RELEASE_TYPE" "$EVENT_NUMBER" "$BASE_SHA"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          RELEASE_TYPE: ${{ github.event.inputs.release == 'no' || github.event.inputs.release == null && 'patch' || github.event.inputs.release }}
          EVENT_NUMBER: ${{ github.event.number }}
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
      - name: Create temporary tag for hatch-vcs
        if: github.event.inputs.release != 'no' && github.event.inputs.release != null
        run: git tag "${STEPS_V_OUTPUTS_VERSION}"
        env:
          STEPS_V_OUTPUTS_VERSION: ${{ steps.v.outputs.version }}
      - name: Build package
        run: uv build --python 3.14 --python-preference only-managed --sdist --wheel . --out-dir dist
      - name: Store the distribution packages
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: ${{ env.dists-artifact-name }}
          path: dist/*
  release:
    timeout-minutes: 30
    name: release
    needs: [build]
    if: github.event.inputs.release != 'no' && github.event.inputs.release != null && github.ref == 'refs/heads/main'
    runs-on: latchkey-small
    environment:
      name: release
      url: https://pypi.org/project/platformdirs/${{ needs.build.outputs.version }}
    permissions:
      id-token: write
      contents: write
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          token: ${{ secrets.RELEASE_TOKEN }} # zizmor: ignore[artipacked]
      - name: Commit changelog and tag
        env:
          CHANGELOG: ${{ needs.build.outputs.changelog }}
          VERSION: ${{ needs.build.outputs.version }}
        run: |
          if git ls-remote --tags origin "refs/tags/$VERSION" | grep -q .; then
            echo "Tag $VERSION already exists, skipping changelog commit"
            exit 0
          fi
          git config user.name "${GITHUB_ACTOR}"
          git config user.email "${GITHUB_ACTOR_ID}+${GITHUB_ACTOR}@users.noreply.github.com"
          header=" $VERSION ($(date -u +%Y-%m-%d))"
          separator=$(printf '%0.s*' $(seq 1 $((${#header} + 1))))
          {
            head -4 docs/changelog.rst
            printf '%s\n%s\n%s\n\n%s\n\n' "$separator" "$header" "$separator" "$CHANGELOG"
            tail -n +5 docs/changelog.rst
          } > docs/changelog.tmp
          mv docs/changelog.tmp docs/changelog.rst
          git add docs/changelog.rst
          git commit -m "Release $VERSION"
          git tag "$VERSION"
          git push origin "$VERSION"
          git push origin main
      - name: Download all the dists
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
        with:
          name: ${{ env.dists-artifact-name }}
          path: dist/
      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
        with:
          skip-existing: true
      - name: Create GitHub release
        env:
          GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
          VERSION: ${{ needs.build.outputs.version }}
        run: |
          if gh release view "$VERSION" > /dev/null 2>&1; then
            echo "Release $VERSION already exists, skipping"
          else
            gh release create "$VERSION" --title="$VERSION" --verify-tag --generate-notes
          fi
 

What changed

This workflow runs 2 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow