{"id":16298793,"url":"https://github.com/kdeldycke/workflows","last_synced_at":"2026-02-22T09:31:46.803Z","repository":{"id":37851794,"uuid":"436658551","full_name":"kdeldycke/workflows","owner":"kdeldycke","description":"⚙️ CLI helpers for GitHubCCC Action + reuseable workflows","archived":false,"fork":false,"pushed_at":"2026-01-22T19:11:52.000Z","size":5814,"stargazers_count":32,"open_issues_count":21,"forks_count":6,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-22T21:07:33.800Z","etag":null,"topics":["build-automation","changelog-formatter","ci-cd","cli","formatting","github-actions","gitignore","labels","linting","mailmap","markdown","mypy","nuitka","packaging","pypi","release-automation","sphinx-doc","typo","uv","workflow-reusable"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kdeldycke.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":null,"funding":".github/funding.yml","license":"license","code_of_conduct":".github/code-of-conduct.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"kdeldycke"}},"created_at":"2021-12-09T15:04:12.000Z","updated_at":"2026-01-22T19:02:38.000Z","dependencies_parsed_at":"2023-11-21T20:30:36.192Z","dependency_job_id":"254fa760-4262-4759-9ad5-33a087e54f43","html_url":"https://github.com/kdeldycke/workflows","commit_stats":null,"previous_names":[],"tags_count":334,"template":false,"template_full_name":null,"purl":"pkg:github/kdeldycke/workflows","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kdeldycke%2Fworkflows","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kdeldycke%2Fworkflows/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kdeldycke%2Fworkflows/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kdeldycke%2Fworkflows/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kdeldycke","download_url":"https://codeload.github.com/kdeldycke/workflows/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kdeldycke%2Fworkflows/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28750630,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T09:00:19.176Z","status":"ssl_error","status_checked_at":"2026-01-25T09:00:04.131Z","response_time":113,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["build-automation","changelog-formatter","ci-cd","cli","formatting","github-actions","gitignore","labels","linting","mailmap","markdown","mypy","nuitka","packaging","pypi","release-automation","sphinx-doc","typo","uv","workflow-reusable"],"created_at":"2024-10-10T20:45:24.677Z","updated_at":"2026-02-22T09:31:46.793Z","avatar_url":"https://github.com/kdeldycke.png","language":"Python","funding_links":["https://github.com/sponsors/kdeldycke"],"categories":[],"sub_categories":[],"readme":"# `gha-utils` CLI + reusable workflows\n\n[![Last release](https://img.shields.io/pypi/v/gha-utils.svg)](https://pypi.org/project/gha-utils/)\n[![Python versions](https://img.shields.io/pypi/pyversions/gha-utils.svg)](https://pypi.org/project/gha-utils/)\n[![Downloads](https://static.pepy.tech/badge/gha_utils/month)](https://pepy.tech/projects/gha_utils)\n[![Unittests status](https://github.com/kdeldycke/workflows/actions/workflows/tests.yaml/badge.svg?branch=main)](https://github.com/kdeldycke/workflows/actions/workflows/tests.yaml?query=branch%3Amain)\n[![Coverage status](https://codecov.io/gh/kdeldycke/workflows/branch/main/graph/badge.svg)](https://app.codecov.io/gh/kdeldycke/workflows)\n\n[Reusable workflows](#reusable-workflows-collection) and a standalone [CLI (`gha-utils`)](#gha-utils-cli) that let you **release Python packages multiple times a day with only 2-clicks**. Designed for `uv`-based Python projects, but usable for other projects too.\n\n[**Maintainer-in-the-loop**](#maintainer-in-the-loop): nothing is done behind your back. A PR or issue is created every time a change is proposed or action is needed.\n\nAutomates:\n\n- Version bumping\n- Changelog management\n- Formatting autofix for: Python, Markdown, JSON, typos\n- Linting: Python types with `mypy`, YAML, `zsh`, GitHub Actions, URLS \u0026 redirects, Awesome lists, secrets\n- Compiling of Python binaries for Linux / macOS / Windows on `x86_64` \u0026 `arm64`\n- Building of Python packages and upload to PyPI\n- Produce attestations\n- Git version tagging and GitHub release creation\n- Synchronization of: `uv.lock`, `.gitignore`, `.mailmap` and Mermaid dependency graph\n- Auto-locking of inactive closed issues\n- Static image optimization\n- Sphinx documentation building \u0026 deployment, and `autodoc` updates\n- Label management, with file-based and content-based rules\n- Awesome list template synchronization\n- Address [GitHub Actions limitations](#github-actions-limitations)\n\n## GitHub Actions limitations\n\nGitHub Actions has several design limitations. This repository works around most of them:\n\n| Limitation                                                  | Status             | Addressed by                                                                                                                                                      |\n| :---------------------------------------------------------- | :----------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| No conditional step groups                                  | ✅ Addressed       | [`project-metadata` job](#what-is-this-project-metadata-job) + [`gha-utils metadata`](#gha-utils-cli)                                                             |\n| Workflow inputs only accept strings                         | ✅ Addressed       | String parsing in [`gha-utils`](#gha-utils-cli)                                                                                                                   |\n| Matrix outputs not cumulative                               | ✅ Addressed       | [`project-metadata`](#what-is-this-project-metadata-job) pre-computes matrices                                                                                    |\n| `cancel-in-progress` evaluated on new run, not old          | ✅ Addressed       | [SHA-based concurrency groups](#concurrency-and-cancellation) in [`release.yaml`](#githubworkflowsreleaseyaml-jobs)                                               |\n| Cross-event concurrency cancellation                        | ✅ Addressed       | [`event_name` in `changelog.yaml` concurrency group](#concurrency-and-cancellation)                                                                               |\n| PR close doesn't cancel runs                                | ✅ Addressed       | [`cancel-runs.yaml`](#githubworkflowscancel-runsyaml-jobs)                                                                                                        |\n| `GITHUB_TOKEN` can't modify workflow files                  | ✅ Addressed       | [`WORKFLOW_UPDATE_GITHUB_PAT` fine-grained PAT](#solution-fine-grained-personal-access-token)                                                                     |\n| Tag pushes from Actions don't trigger workflows             | ✅ Addressed       | [Custom PAT](#solution-fine-grained-personal-access-token) for tag operations                                                                                     |\n| Default input values not propagated across events           | ✅ Addressed       | Manual defaults in `env:` section                                                                                                                                 |\n| `head_commit` only has latest commit in multi-commit pushes | ✅ Addressed       | [`gha-utils metadata`](#what-is-this-project-metadata-job) extracts full commit range                                                                             |\n| `actions/checkout` uses merge commit for PRs                | ✅ Addressed       | Explicit `ref: github.event.pull_request.head.sha`                                                                                                                |\n| Multiline output encoding fragile                           | ✅ Addressed       | Random delimiters in `gha_utils/github.py`                                                                                                                        |\n| Branch deletion doesn't cancel runs                         | ❌ Not addressed   | Same root cause as PR close; partially mitigated by [`cancel-runs.yaml`](#githubworkflowscancel-runsyaml-jobs) since branch deletion typically follows PR closure |\n| No native way to depend on all matrix jobs completing       | ❌ Not addressed   | GitHub limitation; use `needs:` with a summary job as workaround                                                                                                  |\n| `actionlint` false positives for runtime env vars           | 🚫 Not addressable | Linter limitation, not GitHub's                                                                                                                                   |\n\n## Quick start\n\n```shell-session\n$ cd my-project\n$ uvx -- gha-utils init\n$ git add . \u0026\u0026 git commit -m \"Bootstrap reusable workflows\" \u0026\u0026 git push\n```\n\nThat's it. The workflows will start running and guide you through any remaining setup (like [creating a `WORKFLOW_UPDATE_GITHUB_PAT` secret](#solution-fine-grained-personal-access-token)) via issues and PRs in your repository.\n\nRun `gha-utils init --help` to see available components and options.\n\n## `gha-utils` CLI\n\n`gha-utils` stands for *GitHub Actions workflows utilities*.\n\n### Try it\n\nThanks to `uv`, you can run it in one command, without installation or venv:\n\n```shell-session\n$ uvx -- gha-utils\nUsage: gha-utils [OPTIONS] COMMAND [ARGS]...\n\nOptions:\n  --time / --no-time    Measure and print elapsed execution time.  [default:\n                        no-time]\n  --color, --ansi / --no-color, --no-ansi\n                        Strip out all colors and all ANSI codes from output.\n                        [default: color]\n  --config CONFIG_PATH  Location of the configuration file. Supports local path\n                        with glob patterns or remote URL.  [default:\n                        ~/Library/Application Support/gha-\n                        utils/*.toml|*.yaml|*.yml|*.json|*.ini]\n  --no-config           Ignore all configuration files and only use command\n                        line parameters and environment variables.\n  --show-params         Show all CLI parameters, their provenance, defaults and\n                        value, then exit.\n  --table-format [aligned|asciidoc|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|html|jira|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|tsv|unsafehtml|vertical|youtrack]\n                        Rendering style of tables.  [default: rounded-outline]\n  --verbosity LEVEL     Either CRITICAL, ERROR, WARNING, INFO, DEBUG.\n                        [default: WARNING]\n  -v, --verbose         Increase the default WARNING verbosity by one level for\n                        each additional repetition of the option.  [default: 0]\n  --version             Show the version and exit.\n  -h, --help            Show this message and exit.\n\nCommands:\n  broken-links       Manage broken links issue lifecycle\n  changelog          Maintain a Markdown-formatted changelog\n  check-renovate     Check Renovate migration prerequisites\n  deps-graph         Generate dependency graph from uv lockfile\n  git-tag            Create and push a Git tag\n  init               Bootstrap a repository to use reusable workflows\n  lint-changelog     Check changelog dates against release dates\n  lint-repo          Run repository consistency checks\n  mailmap-sync       Update Git's .mailmap file with missing contributors\n  metadata           Output project metadata\n  pr-body            Generate PR body with workflow metadata\n  release-prep       Prepare files for a release\n  setup-guide        Manage setup guide issue lifecycle\n  sponsor-label      Label issues/PRs from GitHub sponsors\n  sync-uv-lock       Re-lock and revert if only timestamp noise changed\n  test-plan          Run a test plan from a file against a binary\n  update-checksums   Update SHA-256 checksums for binary downloads\n  update-gitignore   Generate .gitignore from gitignore.io templates\n  verify-binary      Verify binary architecture using exiftool\n  version-check      Check if a version bump is allowed\n  workflow           Manage downstream workflow caller files\n```\n\n```shell-session\n$ uvx -- gha-utils --version\ngha-utils, version 5.9.1\n```\n\nThat's the best way to get started with `gha-utils` and experiment with it.\n\n\u003e [!TIP]\n\u003e Development versions use a `.devN` suffix per [PEP 440](https://peps.python.org/pep-0440/#developmental-releases). When running from a Git clone, the short commit hash is appended as a local version identifier (e.g., `5.9.2.dev0+abc1234`).\n\n### Executables\n\nTo ease deployment, standalone executables of `gha-utils`'s latest version are available as direct downloads for several platforms and architectures:\n\n| Platform    | `arm64`                                                                                                                               | `x86_64`                                                                                                                          |\n| :---------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |\n| **Linux**   | [Download `gha-utils-linux-arm64.bin`](https://github.com/kdeldycke/workflows/releases/latest/download/gha-utils-linux-arm64.bin)     | [Download `gha-utils-linux-x64.bin`](https://github.com/kdeldycke/workflows/releases/latest/download/gha-utils-linux-x64.bin)     |\n| **macOS**   | [Download `gha-utils-macos-arm64.bin`](https://github.com/kdeldycke/workflows/releases/latest/download/gha-utils-macos-arm64.bin)     | [Download `gha-utils-macos-x64.bin`](https://github.com/kdeldycke/workflows/releases/latest/download/gha-utils-macos-x64.bin)     |\n| **Windows** | [Download `gha-utils-windows-arm64.exe`](https://github.com/kdeldycke/workflows/releases/latest/download/gha-utils-windows-arm64.exe) | [Download `gha-utils-windows-x64.exe`](https://github.com/kdeldycke/workflows/releases/latest/download/gha-utils-windows-x64.exe) |\n\nThat way you have a chance to try it out without installing Python or `uv`. Or embed it in your CI/CD pipelines running on minimal images. Or run it on old platforms without worrying about dependency hell.\n\n\u003e [!NOTE]\n\u003e ABI targets:\n\u003e\n\u003e ```shell-session\n\u003e $ file ./gha-utils-*\n\u003e ./gha-utils-linux-arm64.bin:   ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=520bfc6f2bb21f48ad568e46752888236552b26a, for GNU/Linux 3.7.0, stripped\n\u003e ./gha-utils-linux-x64.bin:     ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=56ba24bccfa917e6ce9009223e4e83924f616d46, for GNU/Linux 3.2.0, stripped\n\u003e ./gha-utils-macos-arm64.bin:   Mach-O 64-bit executable arm64\n\u003e ./gha-utils-macos-x64.bin:     Mach-O 64-bit executable x86_64\n\u003e ./gha-utils-windows-arm64.exe: PE32+ executable (console) Aarch64, for MS Windows\n\u003e ./gha-utils-windows-x64.exe:   PE32+ executable (console) x86-64, for MS Windows\n\u003e ```\n\n### Development version\n\nTo play with the latest development version of `gha-utils`, you can run it directly from the repository:\n\n```shell-session\n$ uvx --from git+https://github.com/kdeldycke/workflows -- gha-utils --version\ngha-utils, version 5.9.2.dev0+3eb8894\n```\n\n## Reusable workflows collection\n\nThis repository contains workflows to automate most of the boring tasks in the form of [reusable GitHub Actions workflows](https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows).\n\n### Example usage\n\nThe fastest way to adopt these workflows is with `gha-utils init` (see [Quick start](#quick-start)). It generates all the thin-caller workflow files for you.\n\nIf you prefer to set up a single workflow manually, create a `.github/workflows/lint.yaml` file [using the `uses` syntax](https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows#calling-a-reusable-workflow):\n\n```yaml\nname: Lint\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    uses: kdeldycke/workflows/.github/workflows/lint.yaml@v5.9.1\n```\n\n\u003e [!IMPORTANT]\n\u003e [Concurrency is already configured](#concurrency-and-cancellation) in the reusable workflows—you don't need to re-specify it in your calling workflow.\n\n### `[tool.gha-utils]` configuration\n\nDownstream projects can customize workflow behavior by adding a `[tool.gha-utils]` section in their `pyproject.toml`:\n\n```toml\n[tool.gha-utils]\nnuitka = false\nnuitka-extra-args = [\n  \"--include-data-files=my_pkg/data/*.json=my_pkg/data/\",\n]\nunstable-targets = [\"linux-arm64\", \"windows-arm64\"]\ntest-plan-file = \"./tests/cli-test-plan.yaml\"\ntimeout = 120\ntest-plan = \"- args: --version\"\ngitignore-location = \"./.gitignore\"\ngitignore-extra-categories = [\"terraform\", \"go\"]\ngitignore-extra-content = '''\njunit.xml\n\n# Claude Code\n.claude/\n'''\ndependency-graph-output = \"./docs/assets/dependencies.mmd\"\nextra-label-files = [\"https://example.com/my-labels.toml\"]\nextra-file-rules = \"docs:\\n  - docs/**\"\nextra-content-rules = \"security:\\n  - '(CVE|vulnerability)'\"\n```\n\n| Option                       | Type      | Default                                           | Description                                                                                                                                          |\n| :--------------------------- | :-------- | :------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nuitka`                     | bool      | `true`                                            | Enable [Nuitka binary compilation](#githubworkflowsreleaseyaml-jobs). Set to `false` for projects with `[project.scripts]` that don't need binaries. |\n| `nuitka-extra-args`          | list[str] | `[]`                                              | Extra Nuitka CLI arguments for binary compilation (e.g., `--include-data-files`, `--include-package-data`). Passed via the build matrix.             |\n| `unstable-targets`           | list[str] | `[]`                                              | Nuitka build targets allowed to fail without blocking the release (e.g., `[\"linux-arm64\"]`).                                                         |\n| `test-plan-file`             | str       | `\"./tests/cli-test-plan.yaml\"`                    | Path to the YAML test plan file for binary testing. Read directly by `test-plan` subcommand; CLI args override.                                      |\n| `timeout`                    | int       | *(none)*                                          | Timeout in seconds for each binary test. Read directly by `test-plan` subcommand; CLI `--timeout` overrides.                                         |\n| `test-plan`                  | str       | *(none)*                                          | Inline YAML test plan for binary testing. Read directly by `test-plan` subcommand; CLI `--plan-file`/`--plan-envvar` override.                       |\n| `gitignore-location`         | str       | `\"./.gitignore\"`                                  | File path of the `.gitignore` to update.                                                                                                             |\n| `gitignore-extra-categories` | list[str] | `[]`                                              | Additional categories to add to the `.gitignore` file (e.g., `[\"terraform\", \"go\"]`).                                                                 |\n| `gitignore-extra-content`    | str       | See [example above](#toolgha-utils-configuration) | Additional content to append to the generated `.gitignore`. Supports TOML multi-line literal strings (`'''...'''`).                                  |\n| `dependency-graph-output`    | str       | `\"./docs/assets/dependencies.mmd\"`                | Location of the generated dependency graph file. Read directly by `deps-graph` subcommand; CLI `--output` overrides.                                 |\n| `extra-label-files`          | list[str] | `[]`                                              | URLs of additional label definition files (JSON, JSON5, TOML, or YAML) downloaded and applied by `labelmaker`.                                       |\n| `extra-file-rules`           | str       | `\"\"`                                              | Additional YAML rules appended to the bundled file-based labeller configuration.                                                                     |\n| `extra-content-rules`        | str       | `\"\"`                                              | Additional YAML rules appended to the bundled content-based labeller configuration.                                                                  |\n\n### [`.github/workflows/autofix.yaml` jobs](https://github.com/kdeldycke/workflows/blob/main/.github/workflows/autofix.yaml)\n\n*Setup* — guide new users through initial configuration:\n\n- **Setup guide** (`setup-guide`)\n\n  - Detects missing `WORKFLOW_UPDATE_GITHUB_PAT` secret and opens an issue with step-by-step setup instructions\n  - Automatically closes the issue once the secret is configured\n  - **Skip**: upstream `kdeldycke/workflows` repo, `workflow_call` events\n\n*Formatters* — rewrite files to enforce canonical style:\n\n- **Format Python** (`format-python`)\n\n  - Auto-formats Python code using [`autopep8`](https://github.com/hhatto/autopep8) and [`ruff`](https://github.com/astral-sh/ruff)\n  - **Requires**:\n    - Python files (`**/*.{py,pyi,pyw,pyx,ipynb}`) in the repository, or\n    - documentation files (`**/*.{markdown,mdown,mkdn,mdwn,mkd,md,mdtxt,mdtext,mdx,rst,tex}`)\n\n- **Format `pyproject.toml`** (`format-pyproject`)\n\n  - Auto-formats `pyproject.toml` using [`pyproject-fmt`](https://github.com/tox-dev/pyproject-fmt)\n  - **Requires**:\n    - Python package with a `pyproject.toml` file\n\n- **Format Markdown** (`format-markdown`)\n\n  - Auto-formats Markdown files using [`mdformat`](https://github.com/hukkin/mdformat)\n  - **Requires**:\n    - Markdown files (`**/*.{markdown,mdown,mkdn,mdwn,mkd,md,mdtxt,mdtext,mdx}`) in the repository\n\n- **Format JSON** (`format-json`)\n\n  - Auto-formats JSON, JSONC, and JSON5 files using [Biome](https://github.com/biomejs/biome)\n  - **Requires**:\n    - JSON files (`**/*.{json,jsonc,json5}`, `**/.code-workspace`, `!**/package-lock.json`) in the repository\n\n*Fixers* — correct or improve existing content in-place:\n\n- **Fix typos** (`fix-typos`)\n\n  - Automatically fixes typos in the codebase using [`typos`](https://github.com/crate-ci/typos)\n\n- **Lint changelog** (`lint-changelog`)\n\n  - Checks and fixes changelog dates and admonitions using [`gha-utils lint-changelog`](https://github.com/kdeldycke/workflows/blob/main/gha_utils/changelog.py)\n\n- **Optimize images** (`optimize-images`)\n\n  - Compresses images in the repository using [`image-actions`](https://github.com/calibreapp/image-actions)\n  - **Requires**:\n    - Image files (`**/*.{jpeg,jpg,png,webp,avif}`) in the repository\n\n*Syncers* — regenerate files from external sources or project state:\n\n- **Update .gitignore** (`update-gitignore`)\n\n  - Regenerates `.gitignore` from [gitignore.io](https://github.com/toptal/gitignore.io) templates using [`gha-utils update-gitignore`](https://github.com/kdeldycke/workflows/blob/main/gha_utils/cli.py)\n  - **Requires**:\n    - A `.gitignore` file in the repository\n\n- **Sync bumpversion config** (`sync-bumpversion`)\n\n  - Syncs the `[tool.bumpversion]` configuration in `pyproject.toml` using [`gha-utils init bumpversion`](https://github.com/kdeldycke/workflows/blob/main/gha_utils/init_project.py)\n  - **Skipped if**:\n    - `[tool.bumpversion]` section already exists in `pyproject.toml`\n\n- **Sync workflows** (`sync-workflows`)\n\n  - Syncs thin-caller workflow files from the upstream [`kdeldycke/workflows`](https://github.com/kdeldycke/workflows) repository using [`gha-utils workflow sync`](https://github.com/kdeldycke/workflows/blob/main/gha_utils/workflow_sync.py)\n  - **Skipped if**:\n    - Repository is [`kdeldycke/workflows`](https://github.com/kdeldycke/workflows) itself (the upstream source)\n\n- **Update `.mailmap`** (`update-mailmap`)\n\n  - Keeps `.mailmap` file up to date with contributors using [`gha-utils mailmap-sync`](https://github.com/kdeldycke/workflows/blob/main/gha_utils/mailmap.py)\n  - **Requires**:\n    - A `.mailmap` file in the repository root\n\n- **Update dependency graph** (`update-deps-graph`)\n\n  - Generates a Mermaid dependency graph of the Python project using [`gha-utils deps-graph`](https://github.com/kdeldycke/workflows/blob/main/gha_utils/deps_graph.py)\n  - **Requires**:\n    - Python package with a `uv.lock` file\n\n- **Update docs** (`update-docs`)\n\n  - Regenerates Sphinx autodoc files using [`sphinx-apidoc`](https://github.com/sphinx-doc/sphinx)\n  - Runs `docs/docs_update.py` if present to generate dynamic content (tables, diagrams, Sphinx directives)\n  - **Requires**:\n    - Python package with a `pyproject.toml` file\n    - `docs` dependency group\n    - Sphinx autodoc enabled (checks for `sphinx.ext.autodoc` in `docs/conf.py`)\n\n- **Sync awesome template** (`sync-awesome-template`)\n\n  - Syncs awesome list projects from the [`awesome-template`](https://github.com/kdeldycke/awesome-template) repository using [`actions-template-sync`](https://github.com/AndreasAugustin/actions-template-sync)\n  - **Requires**:\n    - Repository name starts with `awesome-`\n    - Repository is not [`awesome-template`](https://github.com/kdeldycke/awesome-template) itself\n\n### [`.github/workflows/autolock.yaml` jobs](https://github.com/kdeldycke/workflows/blob/main/.github/workflows/autolock.yaml)\n\n- **Lock inactive threads** (`lock`)\n\n  - Automatically locks closed issues and PRs after 90 days of inactivity using [`lock-threads`](https://github.com/dessant/lock-threads)\n\n### [`.github/workflows/debug.yaml` jobs](https://github.com/kdeldycke/workflows/blob/main/.github/workflows/debug.yaml)\n\n- **Dump context** (`dump-context`)\n\n  - Dumps GitHub Actions context and runner environment info across all build targets using [`ghaction-dump-context`](https://github.com/crazy-max/ghaction-dump-context)\n  - Useful for debugging runner differences and CI environment issues\n  - **Runs on**:\n    - Push to `main` (only when `debug.yaml` itself changes)\n    - Monthly schedule\n    - Manual dispatch\n    - `workflow_call` from downstream repositories\n\n### [`.github/workflows/cancel-runs.yaml` jobs](https://github.com/kdeldycke/workflows/blob/main/.github/workflows/cancel-runs.yaml)\n\n- **Cancel PR runs** (`cancel-runs`)\n\n  - Cancels all in-progress and queued workflow runs for a PR's branch when the PR is closed\n  - Prevents wasted CI resources from long-running jobs (e.g. Nuitka binary builds) that continue after a PR is closed\n  - GitHub Actions does not natively cancel runs on PR close — the `concurrency` mechanism only triggers cancellation when a *new* run enters the same group\n\n### [`.github/workflows/changelog.yaml` jobs](https://github.com/kdeldycke/workflows/blob/main/.github/workflows/changelog.yaml)\n\n- **Bump versions** (`bump-versions`)\n\n  - Creates PRs for minor and major version bumps using [`bump-my-version`](https://github.com/callowayproject/bump-my-version)\n  - Syncs `uv.lock` to include the new version in the same commit\n  - Uses commit message parsing as fallback when tags aren't available yet\n  - **Requires**:\n    - `bump-my-version` configuration in `pyproject.toml`\n    - A `changelog.md` file\n  - **Runs on**:\n    - Schedule (daily at 6:00 UTC)\n    - Manual dispatch\n    - After `release.yaml` workflow completes successfully (via `workflow_run` trigger, to ensure tags exist before checking bump eligibility). Checks out the latest `main` HEAD, not the triggering workflow's commit.\n\n- **Prepare release** (`prepare-release`)\n\n  - Creates a release PR with two commits: a **freeze commit** that freezes everything to the release version, and an **unfreeze commit** that reverts to development references and bumps the patch version\n  - Uses [`bump-my-version`](https://github.com/callowayproject/bump-my-version) and [`gha-utils changelog`](https://github.com/kdeldycke/workflows/blob/main/gha_utils/changelog.py)\n  - Must be merged with \"Rebase and merge\" (not squash) — the auto-tagging job needs both commits separate\n  - **Requires**:\n    - `bump-my-version` configuration in `pyproject.toml`\n    - A `changelog.md` file\n  - **Runs on**:\n    - Push to `main` (when `changelog.md`, `pyproject.toml`, or workflow files change)\n    - Manual dispatch\n    - `workflow_call` from downstream repositories\n\n### [`.github/workflows/docs.yaml` jobs](https://github.com/kdeldycke/workflows/blob/main/.github/workflows/docs.yaml)\n\nThese jobs require a `docs` [dependency group](https://docs.astral.sh/uv/concepts/projects/dependencies/#dependency-groups) in `pyproject.toml` so they can determine the right Sphinx version to install and its dependencies:\n\n```toml\n[dependency-groups]\ndocs = [\n    \"furo\",\n    \"myst-parser\",\n    \"sphinx\",\n    …\n]\n```\n\n- **Deploy Sphinx doc** (`deploy-docs`)\n\n  - Builds Sphinx-based documentation and publishes it to GitHub Pages using [`sphinx`](https://github.com/sphinx-doc/sphinx) and [`gh-pages`](https://github.com/peaceiris/actions-gh-pages)\n  - **Requires**:\n    - Python package with a `pyproject.toml` file\n    - `docs` dependency group\n    - Sphinx configuration file at `docs/conf.py`\n\n- **Sphinx linkcheck** (`check-sphinx-links`)\n\n  - Runs Sphinx's built-in [`linkcheck`](https://www.sphinx-doc.org/en/master/usage/builders/index.html#sphinx.builders.linkcheck.CheckExternalLinksBuilder) builder to detect broken auto-generated links (intersphinx, autodoc, type annotations) that Lychee cannot see\n  - Creates/updates issues for broken documentation links found\n  - **Requires**:\n    - Python package with a `pyproject.toml` file\n    - `docs` dependency group\n    - Sphinx configuration file at `docs/conf.py`\n  - **Skipped for**:\n    - Pull requests\n    - `prepare-release` branch\n    - Post-release version bump commits\n\n- **Check broken links** (`check-broken-links`)\n\n  - Checks for broken links in documentation using [`lychee`](https://github.com/lycheeverse/lychee)\n  - Creates/updates issues for broken links found\n  - **Requires**:\n    - Documentation files (`**/*.{markdown,mdown,mkdn,mdwn,mkd,md,mdtxt,mdtext,mdx,rst,tex}`) in the repository\n  - **Skipped for**:\n    - All PRs (only runs on push to main)\n    - `prepare-release` branch\n    - Post-release bump commits\n\n### [`.github/workflows/labels.yaml` jobs](https://github.com/kdeldycke/workflows/blob/main/.github/workflows/labels.yaml)\n\n- **Sync labels** (`sync-labels`)\n\n  - Synchronizes repository labels using [`labelmaker`](https://github.com/jwodder/labelmaker)\n  - Uses [`labels.toml`](https://github.com/kdeldycke/workflows/blob/main/gha_utils/data/labels.toml) with multiple profiles:\n    - `default` profile applied to all repositories\n    - `awesome` profile additionally applied to `awesome-*` repositories\n\n- **File-based PR labeller** (`file-labeller`)\n\n  - Automatically labels PRs based on changed file paths using [`labeler`](https://github.com/actions/labeler)\n  - **Skipped for**:\n    - `prepare-release` branch\n    - Bot-created PRs\n\n- **Content-based labeller** (`content-labeller`)\n\n  - Automatically labels issues and PRs based on title and body content using [`issue-labeler`](https://github.com/github/issue-labeler)\n  - **Skipped for**:\n    - `prepare-release` branch\n    - Bot-created PRs\n\n- **Tag sponsors** (`sponsor-labeller`)\n\n  - Adds a `💖 sponsors` label to issues and PRs from sponsors using the GitHub GraphQL API\n  - **Skipped for**:\n    - `prepare-release` branch\n    - Bot-created PRs\n\n### [`.github/workflows/lint.yaml` jobs](https://github.com/kdeldycke/workflows/blob/main/.github/workflows/lint.yaml)\n\n- **Lint repository metadata** (`lint-repo`)\n\n  - Validates repository metadata (package name, Sphinx docs, project description) using [`gha-utils lint-repo`](https://github.com/kdeldycke/workflows/blob/main/gha_utils/cli.py). Reads `pyproject.toml` directly.\n  - **Requires**:\n    - Python package (with a `pyproject.toml` file)\n\n- **Lint types** (`lint-types`)\n\n  - Type-checks Python code using [`mypy`](https://github.com/python/mypy)\n  - **Requires**:\n    - Python files (`**/*.{py,pyi,pyw,pyx,ipynb}`) in the repository\n  - **Skipped for**:\n    - `prepare-release` branch\n\n- **Lint YAML** (`lint-yaml`)\n\n  - Lints YAML files using [`yamllint`](https://github.com/adrienverge/yamllint)\n  - **Requires**:\n    - YAML files (`**/*.{yaml,yml}`) in the repository\n  - **Skipped for**:\n    - `prepare-release` branch\n    - Bot-created PRs\n\n- **Lint Zsh** (`lint-zsh`)\n\n  - Syntax-checks Zsh scripts using `zsh --no-exec`\n  - **Requires**:\n    - Zsh files (`**/*.zsh`) in the repository\n  - **Skipped for**:\n    - `prepare-release` branch\n    - Bot-created PRs\n\n- **Lint GitHub Actions** (`lint-github-actions`)\n\n  - Lints workflow files using [`actionlint`](https://github.com/rhysd/actionlint) and [`shellcheck`](https://github.com/koalaman/shellcheck)\n  - **Requires**:\n    - Workflow files (`.github/workflows/**/*.{yaml,yml}`) in the repository\n  - **Skipped for**:\n    - `prepare-release` branch\n    - Bot-created PRs\n\n- **Lint Awesome list** (`lint-awesome`)\n\n  - Lints awesome lists using [`awesome-lint`](https://github.com/sindresorhus/awesome-lint)\n  - **Requires**:\n    - Repository name starts with `awesome-`\n    - Repository is not [`awesome-template`](https://github.com/kdeldycke/awesome-template) itself\n  - **Skipped for**:\n    - `prepare-release` branch\n\n- **Lint secrets** (`lint-secrets`)\n\n  - Scans for leaked secrets using [`gitleaks`](https://github.com/gitleaks/gitleaks)\n  - **Skipped for**:\n    - `prepare-release` branch\n    - Bot-created PRs\n\n### [`.github/workflows/release.yaml` jobs](https://github.com/kdeldycke/workflows/blob/main/.github/workflows/release.yaml)\n\n[Release Engineering is a full-time job, and full of edge-cases](https://web.archive.org/web/20250126113318/https://blog.axo.dev/2023/02/cargo-dist) that nobody wants to deal with. This workflow automates most of it for Python projects.\n\n**Cross-platform binaries** — Targets 6 platform/architecture combinations (Linux/macOS/Windows × `x86_64`/`arm64`). Unstable targets use `continue-on-error` so builds don't fail on experimental platforms. Job names are prefixed with ✅ (stable, must pass) or ⁉️ (unstable, allowed to fail) for quick visual triage in the GitHub Actions UI.\n\n- **Detect squash merge** (`detect-squash-merge`)\n\n  - Detects squash-merged release PRs, opens a GitHub issue to notify the maintainer, and fails the workflow\n  - The release is effectively skipped: `create-tag` only matches commits with the `[changelog] Release v` prefix, so no tag, PyPI publish, or GitHub release is created from a squash merge\n  - The net effect of squashing freeze + unfreeze leaves `main` in a valid state for the next development cycle; the maintainer just releases the next version when ready\n  - **Runs on**:\n    - Push to `main` only\n\n- **Build package** (`build-package`)\n\n  - Builds Python wheel and sdist packages using [`uv build`](https://github.com/astral-sh/uv)\n  - **Requires**:\n    - Python package with a `pyproject.toml` file\n\n- **Compile binaries** (`compile-binaries`)\n\n  - Compiles standalone binaries using [`Nuitka`](https://github.com/Nuitka/Nuitka) for Linux/macOS/Windows on `x64`/`arm64`\n  - On release pushes, each binary generates an attestation and uploads itself directly to the GitHub release as its build completes — decoupled from `create-release`\n  - **Requires**:\n    - Python package with [CLI entry points](https://docs.astral.sh/uv/concepts/projects/config/#entry-points) defined in `pyproject.toml`\n  - **Skipped if** `[tool.gha-utils] nuitka = false` is set in `pyproject.toml` (for projects with CLI entry points that don't need standalone binaries)\n  - **Skipped for** branches that don't affect code:\n    - `update-mailmap` (`.mailmap` changes)\n    - `format-markdown` (documentation formatting)\n    - `format-json` (JSON formatting)\n    - `update-gitignore` (`.gitignore` updates)\n    - `optimize-images` (image optimization)\n    - `update-deps-graph` (dependency graph docs)\n\n- **Test binaries** (`test-binaries`)\n\n  - Runs test plans against compiled binaries using [`gha-utils test-plan`](https://github.com/kdeldycke/workflows/blob/main/gha_utils/test_plan.py)\n  - **Requires**:\n    - Compiled binaries from `compile-binaries` job\n    - Test plan file (default: `./tests/cli-test-plan.yaml`)\n  - **Skipped for**:\n    - Same branches as `compile-binaries`\n\n- **Create tag** (`create-tag`)\n\n  - Creates a Git tag for the release version\n  - **Requires**:\n    - Push to `main` branch\n    - Release commits matrix from [`gha-utils metadata`](https://github.com/kdeldycke/workflows/blob/main/gha_utils/metadata.py)\n\n- **Publish to PyPI** (`publish-pypi`)\n\n  - Uploads packages to PyPI with attestations using [`uv publish`](https://github.com/astral-sh/uv)\n  - **Requires**:\n    - `PYPI_TOKEN` secret\n    - Built packages from `build-package` job\n\n- **Create release** (`create-release`)\n\n  - Creates a GitHub release with the Python package attached using [`action-gh-release`](https://github.com/softprops/action-gh-release)\n  - Binaries are attached independently by each `compile-binaries` matrix entry as they complete\n  - **Requires**:\n    - Successful `create-tag` job\n\n### [`.github/workflows/renovate.yaml` jobs](https://github.com/kdeldycke/workflows/blob/main/.github/workflows/renovate.yaml)\n\n- **Sync bundled config** (`sync-bundled-config`)\n\n  - Keeps the bundled `gha_utils/data/renovate.json5` in sync with the root `renovate.json5`\n  - **Only runs in**:\n    - The `kdeldycke/workflows` repository\n\n- **Migrate to Renovate** (`migrate-to-renovate`)\n\n  - Automatically migrates from Dependabot to Renovate by creating a PR that:\n    - Exports `renovate.json5` configuration file (if missing)\n    - Removes `.github/dependabot.yaml` or `.github/dependabot.yml` (if present)\n  - PR body includes a prerequisites status table showing:\n    - What this PR fixes (config file creation, Dependabot removal)\n    - What needs manual action (security updates settings, token permissions)\n    - Links to relevant settings pages for easy access\n  - Uses [`peter-evans/create-pull-request`](https://github.com/peter-evans/create-pull-request) for consistent PR creation\n  - **Skipped if**:\n    - No changes needed (`renovate.json5` already exists and no Dependabot config is present)\n\n- **Renovate** (`renovate`)\n\n  - Validates prerequisites before running (fails if not met):\n    - `renovate.json5` configuration exists\n    - No Dependabot config file present\n    - Dependabot security updates disabled\n  - Runs self-hosted [Renovate](https://github.com/renovatebot/renovate) to update dependencies\n  - Creates PRs for outdated dependencies with stabilization periods\n  - Handles security vulnerabilities via `vulnerabilityAlerts`\n  - **Requires**:\n    - `WORKFLOW_UPDATE_GITHUB_PAT` secret with Dependabot alerts permission\n\n- **Sync `uv.lock`** (`sync-uv-lock`)\n\n  - Runs `uv lock --upgrade` to update transitive dependencies to their latest allowed versions using [`gha-utils sync-uv-lock`](https://github.com/kdeldycke/workflows/blob/main/gha_utils/renovate.py)\n  - Only creates a PR when the lock file contains real dependency changes (timestamp-only noise is detected and skipped)\n  - Replaces Renovate's `lockFileMaintenance`, which cannot reliably revert noise-only changes\n  - **Requires**:\n    - Python package with a `pyproject.toml` file\n\n### What is this `project-metadata` job?\n\nMost jobs in this repository depend on a shared parent job called `project-metadata`. It runs first to extract contextual information, reconcile and combine it, and expose it for downstream jobs to consume.\n\nThis expands the capabilities of GitHub Actions, since it allows to:\n\n- Share complex data across jobs (like build matrix)\n- Remove limitations of conditional jobs\n- Allow for runner introspection\n- Fix quirks (like missing environment variables, events/commits mismatch, merge commits, etc.)\n\nThis job relies on the [`gha-utils metadata` command](https://github.com/kdeldycke/workflows/blob/main/gha_utils/metadata.py) to gather data from multiple sources:\n\n- **Git**: current branch, latest tag, commit messages, changed files\n- **GitHub**: event type, actor, PR labels\n- **Environment**: OS, architecture\n- **`pyproject.toml`**: project name, version, entry points\n\n\u003e [!IMPORTANT]\n\u003e This flexibility comes at the cost of:\n\u003e\n\u003e - Making the whole workflow a bit more computationally intensive\n\u003e - Introducing a small delay at the beginning of the run\n\u003e - Preventing child jobs to run in parallel before its completion\n\u003e\n\u003e But is worth it given how [GitHub Actions can be frustrating](https://nesbitt.io/2025/12/06/github-actions-package-manager.html).\n\n## How does it work?\n\n### `uv` everywhere\n\nAll Python dependencies and CLIs are installed via [`uv`](https://github.com/astral-sh/uv) for speed and reproducibility.\n\n### Smart job skipping\n\nJobs are guarded by conditions to skip unnecessary steps: file type detection (only lint Python if `.py` files exist), branch filtering (`prepare-release` skipped for most linting), and bot detection.\n\n### Maintainer-in-the-loop\n\nWorkflows never commit directly or act silently. Every proposed change creates a PR; every action needed opens an issue. You review and decide — nothing lands without your approval.\n\n### Configurable with sensible defaults\n\nWorkflows accept `inputs` for customization while providing defaults that work out of the box. Downstream projects can further customize behavior via [`[tool.gha-utils]` configuration](#toolgha-utils-configuration) in `pyproject.toml`.\n\n### Idempotent operations\n\nSafe to re-run: tag creation skips if already exists, version bumps have eligibility checks, PRs update existing branches.\n\n### Graceful degradation\n\nFallback tokens (`secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN`) and `continue-on-error` for unstable targets. Job names use emoji prefixes for at-a-glance status: **✅** for stable jobs that must pass, **⁉️** for unstable jobs (e.g., experimental Python versions, unreleased platforms) that are expected to fail and won't block the workflow.\n\n### Dogfooding\n\nThis repository uses these workflows for itself.\n\n### Dependency strategy\n\nAll dependencies are pinned to specific versions for stability, reproducibility, and security.\n\n#### Pinning mechanisms\n\n| Mechanism                   | What it pins                | How it's updated  |\n| :-------------------------- | :-------------------------- | :---------------- |\n| `uv.lock`                   | Project dependencies        | Renovate PRs      |\n| Hard-coded versions in YAML | GitHub Actions, npm, Python | Renovate PRs      |\n| `uv --exclude-newer` option | Transitive dependencies     | Time-based window |\n| Tagged workflow URLs        | Remote workflow references  | Release process   |\n| `--from . gha-utils`        | CLI from local source       | Release freeze    |\n\n#### Hard-coded versions in workflows\n\nGitHub Actions and npm packages are pinned directly in YAML files:\n\n```yaml\n  - uses: actions/checkout@v6.0.1        # Pinned action\n  - run: npm install eslint@9.39.1       # Pinned npm package\n```\n\nRenovate's `github-actions` manager handles action updates, and a [custom regex manager](https://github.com/kdeldycke/workflows/blob/main/renovate.json5) handles npm packages pinned inline in workflow files.\n\n#### Renovate cooldowns\n\nTo avoid update fatigue, and [mitigate supply chain attacks](https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns), [`renovate.json5`](https://github.com/kdeldycke/workflows/blob/main/renovate.json5) uses stabilization periods (with prime numbers to stagger updates).\n\nThis ensures major updates get more scrutiny while patches flow through faster.\n\n#### `uv.lock` and `--exclude-newer`\n\nThe `uv.lock` file pins all project dependencies, and Renovate keeps it in sync.\n\nThe `--exclude-newer` flag ignores packages released in the last 7 days, providing a buffer against freshly-published broken releases.\n\n#### Tagged workflow URLs\n\nWorkflows in this repository are **self-referential**. The [`prepare-release`](https://github.com/kdeldycke/workflows/blob/main/.github/workflows/changelog.yaml) job's freeze commit rewrites workflow URL references from `main` to the release tag, ensuring released versions reference immutable URLs. The unfreeze commit reverts them back to `main` for development.\n\n### Permissions and token\n\nThis repository updates itself via GitHub Actions. But updating its own YAML files in `.github/workflows` is forbidden by default, and we need extra permissions.\n\n#### Why `permissions:` isn't enough\n\nUsually, to grant special permissions to some jobs, you use the [`permissions` parameter in workflow](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions) files:\n\n```yaml\non: (…)\n\njobs:\n  my-job:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      pull-requests: write\n    steps: (…)\n```\n\nBut `contents: write` doesn't allow write access to workflow files in `.github/`. The `actions: write` permission only covers workflow *runs*, not their YAML source files. Even `permissions: write-all` doesn't work.\n\nYou will always end up with this error:\n\n```text\n! [remote rejected] branch_xxx -\u003e branch_xxx (refusing to allow a GitHub App to create or update workflow `.github/workflows/my_workflow.yaml` without `workflows` permission)\n\nerror: failed to push some refs to 'https://github.com/kdeldycke/my-repo'\n```\n\n\u003e [!NOTE]\n\u003e The **Settings → Actions → General → Workflow permissions** setting on your repository has no effect on this issue. Even with \"Read and write permissions\" enabled, the default `GITHUB_TOKEN` cannot modify workflow files—that's a hard security boundary enforced by GitHub:\n\u003e ![](docs/assets/repo-workflow-permissions.png)\n\n#### What needs the PAT\n\nModifying workflow files is the primary reason for the PAT, but it serves additional purposes. Events triggered by `GITHUB_TOKEN` [don't start new workflow runs](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow), so operations like tag pushes also need the PAT to trigger downstream workflows. Renovate requires several additional permissions for its full feature set.\n\nJobs that use `WORKFLOW_UPDATE_GITHUB_PAT`:\n\n| Workflow         | Job                    | Reason                                                                    |\n| :--------------- | :--------------------- | :------------------------------------------------------------------------ |\n| `autofix.yaml`   | Fix typos              | Create PR touching `.github/workflows/` files                             |\n| `autofix.yaml`   | Sync workflows         | Create PR updating workflow caller files                                  |\n| `autofix.yaml`   | Sync awesome template  | Checkout and sync including workflow files                                |\n| `changelog.yaml` | Prepare release        | Create release PR freezing versions in workflow files                     |\n| `release.yaml`   | Create tag             | Push tag that triggers `on.push.tags` workflows                           |\n| `release.yaml`   | Publish GitHub release | Create release that triggers downstream workflows                         |\n| `renovate.yaml`  | Renovate               | Manage dependency PRs, status checks, dashboard, and vulnerability alerts |\n\nEach token permission maps to specific needs:\n\n| Permission            | Needed for                                                                            |\n| :-------------------- | :------------------------------------------------------------------------------------ |\n| **Workflows**         | All PR-creating jobs that touch `.github/workflows/` files                            |\n| **Contents**          | Tag pushes, release publishing, PR branch creation                                    |\n| **Pull requests**     | All PR-creating jobs (sync-workflows, fix-typos, prepare-release, Renovate)           |\n| **Commit statuses**   | Renovate `stability-days` status checks                                               |\n| **Dependabot alerts** | Renovate vulnerability alert reading                                                  |\n| **Issues**            | Renovate [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) |\n| **Metadata**          | Required for all fine-grained token API operations                                    |\n\nAll jobs fall back to `GITHUB_TOKEN` when the PAT is unavailable (`secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN`), but operations requiring the `workflows` permission or workflow triggering will silently fail.\n\n#### Solution: Fine-grained Personal Access Token\n\nTo bypass these limitations, create a custom access token called `WORKFLOW_UPDATE_GITHUB_PAT`. It replaces the default `secrets.GITHUB_TOKEN` [in steps that need elevated permissions](https://github.com/search?q=repo%3Akdeldycke%2Fworkflows%20WORKFLOW_UPDATE_GITHUB_PAT\u0026type=code).\n\n##### Step 1: Create the token\n\n1. Go to **GitHub → Settings → Developer Settings → Personal Access Tokens → [Fine-grained tokens](https://github.com/settings/personal-access-tokens)**\n\n2. Click **Generate new token**\n\n3. Configure:\n\n   | Field                 | Value                                                                                    |\n   | :-------------------- | :--------------------------------------------------------------------------------------- |\n   | **Token name**        | `workflow-self-update` (or similar descriptive name)                                     |\n   | **Expiration**        | Choose based on your security policy                                                     |\n   | **Repository access** | Select **Only select repositories** and choose the repos that need workflow self-updates |\n\n4. Click **Add permissions**:\n\n   | Permission            | Access                  |\n   | :-------------------- | :---------------------- |\n   | **Commit statuses**   | Read and Write          |\n   | **Contents**          | Read and Write          |\n   | **Dependabot alerts** | Read-only               |\n   | **Issues**            | Read and Write          |\n   | **Metadata**          | Read-only *(mandatory)* |\n   | **Pull requests**     | Read and Write          |\n   | **Workflows**         | Read and Write          |\n\n   \u003e [!IMPORTANT]\n   \u003e The **Workflows** permission is the key. This is the *only* place where you can grant it—it's not available via the `permissions:` parameter in YAML files.\n   \u003e\n   \u003e The **Commit statuses** permission is required by Renovate to set status checks (e.g., `renovate/stability-days`) on commits.\n   \u003e\n   \u003e The **Dependabot alerts** permission allows Renovate to read vulnerability alerts and create security update PRs, replacing Dependabot security updates.\n   \u003e\n   \u003e The **Issues** permission is required by Renovate to create and update the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) issue.\n\n5. Click **Generate token** and copy the `github_pat_XXXX` value\n\n##### Step 2: Add the secret to your repository\n\n1. Go to your repository → **Settings → Security → Secrets and variables → Actions**\n2. Click **New repository secret**\n3. Set:\n   - **Name**: `WORKFLOW_UPDATE_GITHUB_PAT`\n   - **Secret**: paste the `github_pat_XXXX` token\n\n##### Step 3: Configure Dependabot settings\n\nGo to your repository → **Settings → Advanced Security → Dependabot** and configure:\n\n| Setting                         | Status      | Reason                                                |\n| :------------------------------ | :---------- | :---------------------------------------------------- |\n| **Dependabot alerts**           | ✅ Enabled  | Renovate reads these alerts to detect vulnerabilities |\n| **Dependabot security updates** | ❌ Disabled | Renovate creates security PRs instead                 |\n| **Grouped security updates**    | ❌ Disabled | Not needed when security updates are disabled         |\n| **Dependabot version updates**  | ❌ Disabled | Renovate handles all version updates                  |\n\n\u003e [!WARNING]\n\u003e Keep **Dependabot alerts** enabled—these are passive notifications that Renovate reads via the API.\n\u003e Disable all other Dependabot features since Renovate handles both security and version updates.\n\n##### Step 4: Verify it works\n\nRe-run your workflow. It should now update files in `.github/workflows/` without the error.\n\n\u003e [!TIP]\n\u003e **For organizations**: Consider using a [machine user account](https://docs.github.com/en/get-started/learning-about-github/types-of-github-accounts#personal-accounts) or a dedicated service account to own the PAT, rather than tying it to an individual's account.\n\n\u003e [!WARNING]\n\u003e **Token expiration**: Fine-grained PATs expire. Set a calendar reminder to rotate the token before expiration, or your workflows will fail silently.\n\n### Concurrency and cancellation\n\nAll workflows use a `concurrency` directive to prevent redundant runs and save CI resources. When a new commit is pushed, any in-progress workflow runs for the same branch or PR are automatically cancelled.\n\nWorkflows are grouped by:\n\n- **Pull requests**: `{workflow-name}-{pr-number}` — Multiple commits to the same PR cancel previous runs\n- **Branch pushes**: `{workflow-name}-{branch-ref}` — Multiple pushes to the same branch cancel previous runs\n\n`release.yaml` uses a stronger protection: release commits get a **unique concurrency group** based on the commit SHA, so they can never be cancelled. This ensures tagging, PyPI publishing, and GitHub release creation complete successfully.\n\nAdditionally, [`cancel-runs.yaml`](#githubworkflowscancel-runsyaml-jobs) actively cancels in-progress and queued runs when a PR is closed. This complements passive concurrency groups, which only trigger cancellation when a *new* run enters the same group — closing a PR doesn't produce such an event.\n\n\u003e [!TIP]\n\u003e For implementation details on how concurrency groups are computed and why `release.yaml` needs special handling, see [`claude.md` § Concurrency implementation](claude.md#concurrency-implementation).\n\n## Used in\n\nCheck these projects to get real-life examples of usage and inspiration:\n\n- ![GitHub stars](https://img.shields.io/github/stars/kdeldycke/awesome-falsehood?label=%E2%AD%90\u0026style=flat-square) [Awesome Falsehood](https://github.com/kdeldycke/awesome-falsehood) - Falsehoods Programmers Believe in.\n- ![GitHub stars](https://img.shields.io/github/stars/kdeldycke/awesome-engineering-team-management?label=%E2%AD%90\u0026style=flat-square) [Awesome Engineering Team Management](https://github.com/kdeldycke/awesome-engineering-team-management) - How to transition from software development to engineering management.\n- ![GitHub stars](https://img.shields.io/github/stars/kdeldycke/awesome-iam?label=%E2%AD%90\u0026style=flat-square) [Awesome IAM](https://github.com/kdeldycke/awesome-iam) - Identity and Access Management knowledge for cloud platforms.\n- ![GitHub stars](https://img.shields.io/github/stars/kdeldycke/awesome-billing?label=%E2%AD%90\u0026style=flat-square) [Awesome Billing](https://github.com/kdeldycke/awesome-billing) - Billing \u0026 Payments knowledge for cloud platforms.\n- ![GitHub stars](https://img.shields.io/github/stars/kdeldycke/meta-package-manager?label=%E2%AD%90\u0026style=flat-square) [Meta Package Manager](https://github.com/kdeldycke/meta-package-manager) - A unifying CLI for multiple package managers.\n- ![GitHub stars](https://img.shields.io/github/stars/kdeldycke/mail-deduplicate?label=%E2%AD%90\u0026style=flat-square) [Mail Deduplicate](https://github.com/kdeldycke/mail-deduplicate) - A CLI to deduplicate similar emails.\n- ![GitHub stars](https://img.shields.io/github/stars/kdeldycke/dotfiles?label=%E2%AD%90\u0026style=flat-square) [dotfiles](https://github.com/kdeldycke/dotfiles) - macOS dotfiles for Python developers.\n- ![GitHub stars](https://img.shields.io/github/stars/kdeldycke/click-extra?label=%E2%AD%90\u0026style=flat-square) [Click Extra](https://github.com/kdeldycke/click-extra) - Extra colorization and configuration loading for Click.\n- ![GitHub stars](https://img.shields.io/github/stars/kdeldycke/workflows?label=%E2%AD%90\u0026style=flat-square) [workflows](https://github.com/kdeldycke/workflows) - Itself. Eat your own dog-food.\n- ![GitHub stars](https://img.shields.io/github/stars/kdeldycke/extra-platforms?label=%E2%AD%90\u0026style=flat-square) [Extra Platforms](https://github.com/kdeldycke/extra-platforms) - Detect platforms and group them by family.\n\nFeel free to send a PR to add your project in this list if you are relying on these scripts.\n\n## Development\n\nSee [`claude.md`](claude.md) for development commands, code style, testing guidelines, and design principles.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkdeldycke%2Fworkflows","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkdeldycke%2Fworkflows","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkdeldycke%2Fworkflows/lists"}