CLI tool
azat-io/actions-up avatar
azat-io/actions-up

actions-up: updating and SHA-pinning the actions your CI depends on

🌊 Interactive CLI tool to update GitHub Actions to latest versions with SHA pinning

651 stars20 forksTypeScriptMIT

At a glance

What is it?
actions-up is a TypeScript CLI that scans a repository's workflows and composite actions for referenced GitHub Actions, checks them against newer releases, and rewrites the references to exact commit SHAs or leaves them on tags. It also updates runs-on labels, resolves independent tag families correctly, and can emit a JSON report for use in a pull request check.
Who is it for?
actions-up is worth adding to any repository with more than a handful of workflow files, because the finding it produces is not that an action is old but that the reference in your YAML is mutable, and a tool that both reports the drift and rewrites it in the right style saves the argument about whether to pin.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is mutable references, not old versions

actions-up is described as an interactive CLI tool to update GitHub Actions to the latest versions with SHA pinning. The README states the mechanism plainly: it scans your workflows and composite actions to discover every referenced GitHub Action, then checks for newer releases, and then interactively upgrades and pins actions to exact commit SHAs for secure, reproducible CI, or preserves tag-style references when you need to stay on tags.

The security motivation section gives the reason in a form that is worth reading before you decide whether this is a convenience tool or a security tool. GitHub Actions run arbitrary code in your CI. If a job has secrets available, any action used in that job can read the environment and exfiltrate those secrets. A compromised action or a mutable version tag is a direct path to leakage.

So the tool addresses two problems at once. An action at an old version might have a known vulnerability, which is the ordinary dependency problem. An action at a mutable tag might have its tag moved to a different commit, which is a supply chain problem that no version comparison would catch, because the reference never changed and the thing it points to did.

The comparison table in the README puts the operational case in numbers: checking each action manually against scanning all workflows in seconds, the risk of using vulnerable versions against SHA pinning for maximum security, and thirty or more minutes per repository against under a minute in total.

The project is MIT licensed, written in TypeScript, and the last push was on 2026-10-21 with the newest release v1.21.0 from 2026-09-24.

Four installation routes, and npx is the one to reach for first

The installation section is short and offers four options, starting with the one that needs nothing installed.

bash
npx actions-up

That runs the tool straight from the npm registry, which is the right choice for a first run because there is no global state to clean up and you see the behaviour before you commit to it.

For a permanent global installation:

bash
npm install -g actions-up

Or per project, as a development dependency:

bash
npm install --save-dev actions-up

Or through Homebrew, which the README lists as an alternative:

bash
brew install actions-up

That last one matters more than it looks. A tool that rewrites your workflow files is one you want to be able to run on any machine without thinking about npm state, and a system package manager gives you that. A developer tool that only exists in the npm global directory tends to end up on stale versions on exactly the machine you least want to be stale on.

The repository layout shows a pnpm monorepo with a core/ directory for the library, a cli/ directory for the command line front end, types/ for the type definitions, and a bin/ directory holding the executable. The package manifest exports the core as the public entry point and registers the binary as actions-up, and the files list ships only the bin and dist directories, so the tests and the build configuration are not part of the published package.

Four modes, and the report mode is the one for CI

The interactive mode is the default and is what the screenshots show. Run it in your repository root and it scans the workflow and composite action files, checks for available updates, shows an interactive list to select from, and applies the selected updates with SHA pinning by default.

For a non-interactive run, the yes flag skips all prompts and updates everything:

bash
npx actions-up --yes
# or
npx actions-up -y

For a check that changes nothing, there is a dry run:

bash
npx actions-up --dry-run

And for machine consumption there is the JSON mode:

bash
npx actions-up --json

The JSON mode has a precise contract that makes it usable as a CI gate. It is report-only, it never writes files, it skips the interactive prompt, and it cannot be combined with the yes flag. That last constraint is a good design decision rather than an inconvenience, because a mode that both reports and writes cannot be used as a check without a risk of it silently modifying a pull request.

The report structure is worth understanding, because it carries more than a list of updates. Alongside updates there are skipped, plus blockedByMode for actions the mode flag held back, blockedByAge for actions the cool-down held back, blockedByTrust for actions whose tag failed a security check, and blockedAsDowngrade for updates that would downgrade an existing SHA pin. The status field describes the actionable updates only, so it can read up-to-date while those blocked lists are not empty, which is the correct semantics for a check that should not fail on a known and deliberately held-back action.

Runner updates are reported separately, with summary.totalRunnerUpdates and summary.totalRunners counting the available updates and the scanned labels, and the updates and summary totals deliberately exclude runners.

Tag families are the subtlety that separates this from a grep and sed

The section on tag families is the most technically interesting part of the README, and it is the reason a general purpose updater gets this wrong.

Some repositories publish several independent tag families at once, the example being npm releases under v1.2.3 and an action under actions-v1.2.3. A candidate from the wrong family resolves to a real commit, but to the wrong artifact. So a reference from a named family is resolved from that family's own tags rather than from the repository's latest release.

The tool applies this to every update style, so actions-v1.2.3 updates to actions-v1.3.0, and a SHA pin is matched by family through the # actions-v1.2.3 comment written next to it. That comment is doing real work: it is how a pinned reference remembers which family it came from, since the SHA alone does not say.

And the failure mode is handled rather than papered over. When the family cannot be resolved, either because it has no other members or because the repository answers with an unrelated family, the reference is listed in the output instead of being rewritten. A tool that guesses here would quietly point your build at the wrong code.

Two other reference handling rules are worth noting. Branch references such as @main or @release/v1 are skipped by default, to avoid changing intentionally floating references, and they are listed in the output so you can see they were considered. Passing --include-branches brings them into the update checks.

The runner image handling works the same way but against a bundled catalogue. Actions Up updates runs-on labels, such as ubuntu-22.04 to ubuntu-24.04, to the newest stable GitHub-hosted image in its bundled catalog. The word bundled is the constraint, since the catalogue travels with the tool rather than being fetched, so a newly published image appears only when the tool is updated.

Discovery reaches beyond the workflows directory

The scan surface is wider than a naive grep over .github/workflows would be, and each of these cases corresponds to a real place an action reference hides.

Auto-discovery covers all workflows in .github/workflows/*.yml, composite actions in .github/actions/*/action.yml, and a root action.yml or action.yaml. The root composite action is the one that gets missed, because it is a top-level file that is not in a workflows directory and is easy to assume is a repository settings file.

Reusable workflows are handled at the job level, so a workflow that calls another workflow with a uses key under a job gets that reference detected and updated too. That is a different code path from a step-level action reference, and a tool that only looks for uses inside a with block will not find it.

The scan root is adjustable with the dir flag, and you can pass it several times to scan several directories, which is how you cover a repository that keeps its workflows somewhere other than .github, such as a .gitea directory or a shared defaults repository:

bash
npx actions-up --dir .gitea
npx actions-up --dir .github --dir ./other/.github

Recursive scanning with the recursive flag goes through YAML workflow and composite action files inside the selected directories, and the README notes the default that applies when recursive is used without dir, which is that the scan starts from the current directory.

The project also has a CI/CD integration story, described as using it in GitHub Actions workflows for automated pull request checks, and the JSON mode is the mechanism for that. The combination is the sensible pipeline: run the check in a workflow, and only update when a human or a scheduled job decides to.

Editorial conclusion

actions-up is worth adding to any repository with more than a handful of workflow files, because the finding it produces is not that an action is old but that the reference in your YAML is mutable, and a tool that both reports the drift and rewrites it in the right style saves the argument about whether to pin. It is the wrong tool if your workflows reference branch names on purpose, since branch references are skipped by default, and it is the wrong tool if you want the newest runner image immediately, because the runner catalogue is bundled rather than fetched. Before you run it, verify three things yourself: that the SHA pin comment format it writes is what your repository already uses, since the family matching depends on those comments, that the report mode you wire into CI is the JSON one and not the interactive one, and that the actions it flags as trusted or untrusted match your own policy, because the tool makes a security judgement about a tag that you may want to make yourself.

Frequently asked questions

What does the actions-up tool do?

It scans your workflows and composite actions to discover every referenced GitHub Action, checks for newer releases, and then interactively upgrades them, pinning to exact commit SHAs by default or preserving tag-style references with the preserve style. It also updates runs-on labels to the newest stable GitHub-hosted image in its bundled catalog.

How do I install actions-up?

Run npx actions-up for a one-off use with nothing installed, or install it globally with npm install -g actions-up, per project with npm install --save-dev actions-up, or through Homebrew with brew install actions-up. The package is MIT licensed and the newest release is v1.21.0.

What does the JSON output mode of actions-up include?

It is report-only, never writes files, skips the interactive prompt and cannot be combined with the yes flag. The report lists updates plus skipped, blockedByMode, blockedByAge, blockedByTrust and blockedAsDowngrade, and reports runner updates separately under runners with summary totals.

What are tag families and why do they matter in actions-up?

Some repositories publish independent tag families at once, such as npm releases under v1.2.3 and an action under actions-v1.2.3. A candidate from the wrong family resolves to a real commit but the wrong artifact, so a reference from a named family is resolved from that family's own tags, and a SHA pin is matched by family through the comment written next to it.

Does actions-up update actions referenced by branch name?

Not by default. Actions pinned to branch refs such as @main or @release/v1 are skipped to avoid changing intentionally floating references, and the skipped entries are listed in the output. Passing --include-branches includes them in the update checks.

Official sources

  1. azat-io/actions-up on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/azat-io-actions-up.svg)](https://hysenlabs.com/projects/azat-io-actions-up)

Was this useful?