GitHub Actions Version Updater
Scans workflow YAML for uses: pins, asks GitHub for a newer release tag, release commit, or default-branch SHA, and rewrites only the version token. Quotes, comments, and line endings stay as they are. Run it as a scheduled Action that opens a pull request, or as the update-gha CLI on your machine.
| GitHub Action | Python package | |
|---|---|---|
| Use this when | You want a scheduled job that opens a pull request | You want a CLI on your machine or in other CI |
| Add it with | uses: saadmk11/github-actions-version-updater@v1.0.0 |
pip install update-gha or uvx update-gha |
| Pull requests | Opened by default | Optional (--pull-request) |
The Marketplace listing and GitHub repo are github-actions-version-updater. The PyPI project is update-gha. Same updater, two ways to run it.
Contents
GitHub Action
Add a workflow to this repository. On a schedule (or when you run it by hand) the action scans uses: pins and opens a pull request with the updates.
Under the hood it is a composite action: it installs update-gha[action] from this checkout into an isolated CPython 3.14 environment and runs update-gha --pull-request. It does not change the job's python on PATH.
v1 is not a Docker image. Self-hosted runners need bash and outbound network so uv can install that isolated interpreter; Docker is not required.
What it does
Like Dependabot, but only for GitHub Actions:
- Finds
uses:pins in.github/workflowsand any extra paths you pass - Looks up a newer release tag, release commit, or default-branch SHA
- Rewrites only the version token — quotes, comments, and line endings stay as they are
- Commits the result and opens a pull request (unless you set
skip_pull_request)
Local actions (./path) and container actions (docker://…) are not updated.
How the action works
- Check out the repository with a token that can write workflow files.
- Scan workflow YAML for
owner/repo@versionpins. - Ask the GitHub API for a newer version, unless the pin is listed in
ignore. - Rewrite matching pins in place.
- Create a branch, push, and open a PR — unless
skip_pull_requestistrue.
Draft, prerelease, and unpublished GitHub releases are ignored.
Quick start
Trigger on a schedule or workflow_dispatch. Save this as .github/workflows/updater.yaml:
name: GitHub Actions Version Updater
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * 0" # every Sunday
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.WORKFLOW_SECRET }}
- name: Run GitHub Actions Version Updater
uses: saadmk11/github-actions-version-updater@v1.0.0
with:
token: ${{ secrets.WORKFLOW_SECRET }}
Version sources
update_version_with chooses what is written back into each uses: pin:
| Value | What is written | Example |
|---|---|---|
release-tag (default) |
Latest published stable release tag | actions/checkout@v4.2.2 |
release-commit-sha |
Commit that the latest stable tag points at | actions/checkout@11bd7190… |
default-branch-sha |
Latest commit on the action's default branch | actions/checkout@11bd7190… |
Release types
release_types limits which SemVer bumps are applied. It only applies to release-tag and release-commit-sha.
| Value | Updates when |
|---|---|
all (default) |
Any newer stable release |
major |
The major component increases (v3 → v4) |
minor |
Same major, higher minor (v4.1 → v4.2) |
patch |
Same major and minor, higher patch (v4.2.1 → v4.2.2) |
You can combine them: minor, patch.
Two cases leave a pin unchanged instead of guessing:
- The current pin is not SemVer, and
release_typesis notall update_version_withisrelease-commit-sha,release_typesis restricted, and the current SHA cannot be matched to a release tag
Action inputs
| Name | Required | Description | Default |
|---|---|---|---|
token |
Yes | Personal access token that can update workflows. See Access token. | — |
committer_username |
No | Git author name on the update commit | github-actions[bot] |
committer_email |
No | Git author email on the update commit | github-actions[bot]@users.noreply.github.com |
commit_message |
No | Commit message | Update GitHub Action Versions |
pull_request_title |
No | Pull request title | Update GitHub Action Versions |
pull_request_branch |
No | PR head branch. If set, that branch is force-pushed with lease. Cannot be main, master, or the repository base branch. |
generated (gh-actions-update-<id>) |
ignore |
No | Comma-separated pins to leave unchanged. Must match the uses value exactly, including the current version (actions/checkout@v4). |
empty |
skip_pull_request |
No | If true, write the updates, print a diff, and fail the job instead of opening a PR. |
false |
update_version_with |
No | release-tag, release-commit-sha, or default-branch-sha |
release-tag |
release_types |
No | all, or any of major, minor, patch |
all |
pull_request_user_reviewers |
No | Comma-separated usernames requested on a new PR | empty |
pull_request_team_reviewers |
No | Comma-separated team slugs requested on a new PR | empty |
pull_request_labels |
No | Comma-separated labels added on a new PR | empty |
extra_workflow_locations |
No | Extra workflow files or directories. Directories are searched recursively. .github/workflows is always scanned if it exists. |
empty |
Reviewers and labels are applied only when this run opens a new pull request. They are skipped if a PR for that branch already exists.
Action outputs
Set only when this run created a pull request. Empty if you skipped the PR, nothing changed, or a PR for the branch already existed.
| Name | Description |
|---|---|
GHA_UPDATE_PR_NUMBER |
Number of the pull request this run created |
pull-request-number |
Same value |
- name: Run GitHub Actions Version Updater
id: update
uses: saadmk11/github-actions-version-updater@v1.0.0
with:
token: ${{ secrets.WORKFLOW_SECRET }}
- name: Use the pull request number
if: steps.update.outputs.GHA_UPDATE_PR_NUMBER
run: echo "Opened PR #${{ steps.update.outputs.GHA_UPDATE_PR_NUMBER }}"
Access token
GitHub's default ${{ secrets.GITHUB_TOKEN }} cannot push changes to workflow files. Create a personal access token, add it to the repository Actions secrets, and pass that secret as token.
Classic PAT
repo— push the branch and open the pull requestworkflow— update workflow files
Fine-grained PAT
- Contents: Read and write
- Workflows: Read and write
- Pull requests: Read and write
- Metadata: Read-only (granted with the permissions above)
Use the same secret on actions/checkout and on this action so the push uses that credential.
Example workflows
All inputs
name: GitHub Actions Version Updater
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * 0"
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.WORKFLOW_SECRET }}
- name: Run GitHub Actions Version Updater
uses: saadmk11/github-actions-version-updater@v1.0.0
with:
token: ${{ secrets.WORKFLOW_SECRET }}
committer_username: "actions-bot"
committer_email: "actions-bot@users.noreply.github.com"
commit_message: "chore: bump GitHub Actions"
pull_request_title: "chore: bump GitHub Actions"
pull_request_branch: "actions-update"
ignore: "actions/checkout@v4, actions/cache@v4"
update_version_with: "release-tag"
release_types: "minor, patch"
pull_request_user_reviewers: "octocat, hubot"
pull_request_team_reviewers: "justice-league"
pull_request_labels: "dependencies, automated"
extra_workflow_locations: "deploy/workflows, path/to/workflow.yaml"
Write updates without a pull request
Writes the files, prints a diff, and fails the job if anything changed. Use this when another step or a person should commit.
- name: Run GitHub Actions Version Updater
uses: saadmk11/github-actions-version-updater@v1.0.0
with:
token: ${{ secrets.WORKFLOW_SECRET }}
skip_pull_request: "true"
Git LFS
If the repository uses Git LFS, check out with lfs: false or remove the LFS hooks before this action runs. Otherwise creating the update branch can fail when the LFS binary is not on the runner.
- uses: actions/checkout@v4
with:
token: ${{ secrets.WORKFLOW_SECRET }}
lfs: false
- name: Remove LFS hooks
run: |
rm -f .git/hooks/post-checkout .git/hooks/pre-push
- name: Run GitHub Actions Version Updater
uses: saadmk11/github-actions-version-updater@v1.0.0
with:
token: ${{ secrets.WORKFLOW_SECRET }}
Alternative
Dependabot can also bump GitHub Actions. Prefer this action when you want SHA pins, default-branch tips, ignore lists that match exact uses values, or one scheduled PR with your own title, reviewers, and labels.
Python package
update-gha is the CLI the Action runs. Use it locally or in another CI system.
- Scans
.github/workflowsplus extra files or directories - Rewrites only the version token (YAML structure, quotes, comments, and line endings stay)
- Three version sources: release tag, release commit SHA, default-branch SHA
- SemVer filters (
major/minor/patch) --check,--dry-run,--diff,--fail-on-update, and JSON output- Optional
--pull-request(installupdate-gha[action]) - Config from CLI flags,
GHA_UPDATE_*environment variables, or[tool.update-gha]inpyproject.toml
Install
Requires Python 3.12 or newer.
# one-off
uvx update-gha --help
pip install update-gha
# in a project
uv add update-gha
uv run update-gha --check
# user-wide tool
uv tool install update-gha
Pull-request mode needs GitPython. Install the extra only if you will pass --pull-request:
pip install 'update-gha[action]'
uv add 'update-gha[action]'
uv tool install --with 'update-gha[action]' update-gha
How the CLI works
- Scan
.github/workflows(and any extra paths) foruses:pins. - Ask GitHub for a newer release or commit, unless the pin is ignored.
- Rewrite only the version token.
- If you passed
--pull-requestand files were written, commit, push, and open a PR.
| Situation | Token needed? |
|---|---|
Public actions, local rewrite / --check / --dry-run |
No. Passing one avoids the unauthenticated API rate limit. |
| Private action repositories | Yes |
--pull-request |
Yes, plus owner/repo (--repository or GITHUB_REPOSITORY) |
Command examples
update-gha # rewrite workflow files in place
update-gha --check # do not write; exit 1 if any pin would change
update-gha --dry-run --diff # do not write; print the plan and a unified diff
update-gha --fail-on-update # write, then exit 1 if anything was written
update-gha --format json
update-gha --ignore 'actions/checkout@v4'
update-gha --update-version-with release-tag --release-types 'minor,patch'
update-gha path/to/extra.yaml
Open a pull request (needs update-gha[action]):
export GITHUB_TOKEN=...
export GITHUB_REPOSITORY=owner/repo
update-gha --pull-request
--token and --repository work instead of those environment variables. Tracked files must be clean so the commit contains only updater changes; untracked files are left alone.
Configuration
Read in this order (later sources lose to earlier ones):
- CLI flags
- Environment variables (
GHA_UPDATE_*, plusGITHUB_TOKEN/GITHUB_REPOSITORY) [tool.update-gha]in the nearestpyproject.toml(walks up from the current directory)- Built-in defaults
export GITHUB_TOKEN=...
export GHA_UPDATE_IGNORE_ACTIONS='actions/checkout@v4'
export GHA_UPDATE_UPDATE_VERSION_WITH=release-tag
export GHA_UPDATE_RELEASE_TYPES='minor,patch'
update-gha
[tool.update-gha]
ignore_actions = ["actions/checkout@v4"]
update_version_with = "release-tag"
release_types = ["minor", "patch"]
extra_workflow_locations = ["deploy/workflows"]
committer_username = "bot"
pull_request_title = "Update GitHub Action versions"
You can also enable pull-request mode with GHA_UPDATE_CREATE_PULL_REQUEST=true. Commit and PR fields (committer_*, pull_request_*, reviewers, labels) are ignored unless that flag or environment variable is on.
CLI options
| Option | Purpose | Default |
|---|---|---|
--token |
GitHub token. Required for --pull-request and private action repos; optional for public lookups. Also GITHUB_TOKEN / GHA_UPDATE_TOKEN. |
unset |
--ignore |
Comma-separated exact uses pins to skip, including the current version. |
empty |
--update-version-with |
release-tag, release-commit-sha, or default-branch-sha. |
release-tag |
--release-types |
major, minor, patch, or all. No effect on default-branch-sha. |
all |
--extra-workflow-locations |
Extra files or directories, comma-separated. Directories are recursive. | empty |
PATHS |
Extra files or directories as positional arguments. Same role as --extra-workflow-locations. |
none |
--check |
Do not write. Exit 1 if any pin would change. | off |
--dry-run |
Do not write. Exit 0 even if updates exist. | off |
--diff |
Print a unified diff. Works with write, --check, and --dry-run. |
off |
--fail-on-update |
Write, then exit 1 if any file was written. Incompatible with --pull-request, --check, and --dry-run. |
off |
--format |
text or json. JSON sends logs to stderr. |
text |
--verbose / --quiet |
Debug logs, or warnings and errors only. Mutually exclusive. | off |
--pull-request |
Commit, push, and open a PR. Needs update-gha[action], --token, --repository, and a clean tracked tree. |
off |
--repository |
owner/repo that receives the PR. Also GITHUB_REPOSITORY. Unused for a local rewrite. |
unset |
--committer-username |
Git author name for the PR commit. | github-actions[bot] |
--committer-email |
Git author email for the PR commit. | github-actions[bot]@users.noreply.github.com |
--commit-message |
Message for the PR commit. | Update GitHub Action Versions |
--pull-request-title |
Title of the pull request. | Update GitHub Action Versions |
--pull-request-branch |
Head branch. Omit for a unique name; a given name is force-pushed. Cannot be the base branch. | generated (gh-actions-update-<id>) |
--pull-request-reviewers |
Usernames requested on a new PR, comma-separated. | empty |
--pull-request-teams |
Team slugs requested on a new PR, comma-separated. | empty |
--pull-request-labels |
Labels added on a new PR, comma-separated. | empty |
See update-gha --help for the full text.
Development
The package lives in src/update_gha. Tests are in tests/. Tooling is configured in pyproject.toml and run with uv (0.11.32 or newer).
Prerequisites
- Python 3.12+ (CI covers 3.12, 3.13, and 3.14;
.python-versionis3.14) - uv 0.11.32 or newer
Setup
git clone https://github.com/saadmk11/github-actions-version-updater.git
cd github-actions-version-updater
uv sync --all-groups --all-extras
That installs the package (editable), the [action] extra (GitPython), and the dev group (pytest, ruff, mypy).
Layout
| Path | Role |
|---|---|
src/update_gha/ |
Core package: scan, rewrite, GitHub lookup, config, models |
src/update_gha/cli/ |
update-gha console script |
src/update_gha/action/ |
Git + pull-request helpers (optional extra) |
tests/ |
Pytest suite; tests/fixtures/ are YAML rewrite pairs |
action.yaml |
Composite GitHub Action that runs this checkout |
.github/workflows/ci.yaml |
Lint and test matrix |
Tests
uv run pytest
Coverage is required at 100% (--cov-fail-under=100). Tests do not hit the live GitHub API; HTTP and git are faked.
uv run pytest tests/test_rewrite.py # one file
uv run pytest tests/test_cli.py::test_help # one test
tests/smoke_test.py is also run by the release workflow against the built wheel and sdist.
Lint and types
uv run ruff check .
uv run ruff format --check .
uv run mypy
Ruff uses a line length of 88. mypy runs in strict mode on src and tests.
To apply fixes:
uv run ruff check --fix .
uv run ruff format .
Pre-commit
.pre-commit-config.yaml runs end-of-file / whitespace checks, Ruff, and mypy. The Ruff and mypy hooks install their own tools, so they work locally and on pre-commit.ci. Install pre-commit separately (it is not in the dev group):
uv tool install pre-commit
pre-commit install
pre-commit run --all-files
Run locally
uv run update-gha --help
uv run update-gha --dry-run --diff
uv run python -m update_gha --check
CI
.github/workflows/ci.yaml runs on pushes and pull requests to main:
- lint —
ruff check,ruff format --check,mypyon Python 3.14 - test —
pyteston Python 3.12, 3.13, and 3.14
Python dependencies are updated by Dependabot (uv). Action pins in this repository are updated by .github/workflows/update-actions.yaml.
License
Released under the MIT License.
Metadata
Release files for update-gha 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| update_gha-1.0.0.tar.gz | 31.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| update_gha-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.1 kB
Release files / update_gha-1.0.0.tar.gz
| Download URL | update_gha-1.0.0.tar.gz |
|---|---|
| Size | 31.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7100f45aca685763fb0857c0e52c73e2848934ddebf6baedd074ad48b0b58f62
|
|
BLAKE2b-256 checksum How to use checksums |
cf7e219a57f877170d91e62ff16c2cb99a365268f0d8f1cc9627b16f22546ccf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / update_gha-1.0.0-py3-none-any.whl
| Download URL | update_gha-1.0.0-py3-none-any.whl |
|---|---|
| Size | 37.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
99c7551c735d723b74ed0c1420f4bb9acb142343ea6d0a68cb84015313e8d523
|
|
BLAKE2b-256 checksum How to use checksums |
b6c2c22bdf8347f6e621d80ce3607fd3a9157405bfe420a1d4788bc0a510ae58
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency log