pinact: A CLI for Pinning GitHub Actions and Reusable Workflows to Full-Length SHAs
pinact is a CLI to edit GitHub Workflow and Composite action files and pin versions of Actions and Reusable Workflows. pinact can also update their versions and verify version annotations.
At a glance
- What is it?
- pinact is a Go CLI that pins GitHub Actions and Reusable Workflows to full-length commit SHAs, updates their versions, and verifies version comments. It offers offline checks, minimum release age validation, and SARIF output for CI integration.
- Who is it for?
- pinact is for teams that want to enforce full-length SHA pinning in GitHub Actions and Reusable Workflows without writing custom scripts. It suits those who need offline checks, minimum release age enforcement, and SARIF output for code scanning.
- 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 Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 14, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The Problem: Unpinned Actions and Reusable Workflows
GitHub Actions files often reference actions with version tags like 'v3' or branches like 'main'. A tag can be moved, and a branch can change at any time, so a workflow that worked yesterday may break today or, worse, run different code without anyone noticing. pinact solves this by rewriting those references to a full-length commit SHA, which is immutable. It also adds a comment with the original version, so you can still see what release you are on. This is a supply chain security measure: a pinned SHA cannot be changed by the action's owner. The tool targets developers and platform teams who maintain many workflow files and want a deterministic, auditable state for their CI.
How pinact Works: From Tag to SHA via GitHub API
pinact parses workflow files and composite action files. It looks for 'uses:' lines that reference an action or a reusable workflow. For each reference, it calls the GitHub API to resolve the tag or branch to a commit SHA. The README shows a diff where 'actions/setup-go@v4' becomes 'actions/setup-go@7b8cf10d4e4a01d4992d18a89f4d7dc5a3e6d6f4 # v4.3.0'. The SHA is the full 40-character commit hash, and the comment preserves the semantic version. The default file set covers '.github/workflows/*.yml' and 'action.yml' files in the root and nested directories up to three levels deep. You can also pass specific files, including text files like 'README.md', which is useful for pinning examples in documentation. The tool does not invent a version: it fetches releases and tags from GitHub, so an internet connection and a token are recommended to avoid rate limits.
Getting Started: Commands and Configuration
Install pinact from the INSTALL.md, then run 'pinact run' to pin files in place. To just check without editing, use 'pinact run -check' or '-fix=false'. For an offline check, add '-no-api', which only verifies that references are already full-length SHAs. To update all actions to their latest versions, run 'pinact run -update'. Configuration lives in '.pinact.yml'. The README shows a 'min_age' block with a 'value' in days and 'always' as a boolean, plus a 'rules' list with per-rule 'min_age' and 'conditions' using an expression like 'ActionRepoOwner == "suzuki-shunsuke"'. You can also set the minimum age via the '-min-age' flag or the 'PINACT_MIN_AGE' environment variable. Include and exclude filters use regular expressions with '-i' and '-e', and you can pass them multiple times. For branch pinning, '--branch-to-tag' takes a regex and converts matching branches to the latest stable tag.
Minimum Release Age: A Supply Chain Guardrail
pinact can enforce a cooldown period for action versions. The '-min-age' option sets a minimum number of days before a release is considered acceptable. This applies in two ways. First, when updating with '-update', versions that are too new are skipped; if no release meets the age, pinact exits with an error. Second, when verifying current versions with '-verify-min-age', pinact checks that the pinned version is old enough. The README notes that verifying current versions can be wasteful, so it only happens when the flag is set or when '.min_age.always' is true. For GitHub Releases, the release's 'PublishedAt' date is used. For tags, pinact checks the commit's 'Committer.Date', which requires an additional API call. This feature is a direct response to the risk of a malicious or buggy release being adopted immediately. It is a concrete, configurable policy that can be tuned per action owner.
Verifying Version Comments and Requiring Them
When pinact pins an action, it adds a comment with the version, like '# v4.3.0'. The '-verify-comment' flag (also '-verify' or '-v') checks that these comments match the actual version of the pinned SHA. This is useful for auditing: a SHA alone does not tell you which release it came from, and the comment is the only human-readable clue. pinact can also require a version comment on SHA-pinned actions, as described in docs/codes/005.md. This is a policy enforcement feature: if someone pins an action with a SHA but omits the comment, pinact can flag or fix it. The README does not specify the exact behavior of the check, but the existence of a dedicated code document suggests it is a distinct failure mode. This is a pragmatic approach to keeping workflow files readable and auditable.
Branch Pinning: A Deliberate Choice with Caveats
By default, pinact does not pin branches like 'main' or 'master'. This is a design decision: branches are moving targets, and pinning them to a SHA would change semantics. The '--branch-to-tag' option lets you opt in for specific branches, using a regular expression. The README warns that the regex is a partial match, so '^main$' is recommended for short names to avoid matching 'mainline'. The branch is converted to the latest stable tag of the action, and pre-releases are used only if no stable tag exists. The '-min-age' setting is honored, so tags within the cooldown window are skipped. This is a useful feature for teams that want to pin everything, but it introduces a policy question: what happens when a new stable tag appears? pinact does not automatically update branch pins unless you run '-update'. The limitation is that branch pinning is a one-way conversion; you cannot tell pinact to keep a branch reference and just verify it.
Limitations and When pinact Is the Wrong Tool
pinact depends on the GitHub API. Without a token, you will hit rate limits quickly, especially on large repositories or frequent CI runs. The '-no-api' mode only checks the syntactic shape of a SHA, so it cannot pin or update anything. That means the offline mode is only useful as a validation gate, not as a fixer. Another limitation: pinact only understands 'uses:' references in YAML workflow files and composite action files. If your workflows use dynamic references, like a variable for the action version, pinact cannot resolve them. The tool also does not handle other supply chain concerns like dependency hashes for container actions or npm packages. If your team uses only version tags and is comfortable with the risk, pinact may add unnecessary friction. The '-min-age' feature can cause CI failures when a new release is used, which might be undesirable for teams that want to move fast.
Alternatives: Comparing pinact to Other Approaches
A common alternative is to use Dependabot or Renovate to update GitHub Actions. Dependabot can open pull requests for action version updates, but it does not pin to a full-length SHA by default; it typically updates to a tag like 'v4'. Renovate has a feature to pin actions to a SHA, but its configuration is more complex and it is a general-purpose dependency updater. pinact is focused solely on pinning and updating GitHub Actions and Reusable Workflows. The key difference is that pinact's primary operation is a rewrite to a SHA, not just an update. Renovate can do this, but it requires a custom manager and regex rules. pinact also offers a built-in offline check and a minimum release age policy, which are not native to Dependabot. If you already use Renovate, you might prefer to extend its configuration rather than introduce a separate tool.
Maintenance and Upgrade Considerations
pinact is a Go binary, so installation is a single file, and updates are straightforward by downloading a new release. The project is under MIT license, which is permissive and allows commercial use. The repository shows recent releases in 2026, with v4.1.1 being the latest, indicating active maintenance. The README references several features that were added in specific versions, such as '--branch-to-tag' in v3.10.0 and SARIF in v3.7.0, so the project is evolving. The main upgrade cost is keeping up with configuration schema changes. For example, the 'min_age' block and 'rules' list are relatively new, and older versions may not support them. You should read the release notes when upgrading to see if any flags or config keys have changed. The tool also has a GitHub Action, 'pinact-action', which suggests it is designed to be used in CI, but the README does not detail how to set it up. That is a gap you would need to investigate before adopting it as a CI gate.
Editorial conclusion
pinact is for teams that want to enforce full-length SHA pinning in GitHub Actions and Reusable Workflows without writing custom scripts. It suits those who need offline checks, minimum release age enforcement, and SARIF output for code scanning. If you don't need pinning or version updates, or if you rely on branch references like 'main' without a policy to pin them, pinact may be overkill. Before adopting, verify that your workflows use standard 'uses' syntax and that you can provide a GitHub token to avoid API rate limits. Check the .pinact.yml schema and the -include/-exclude options to ensure they cover your action naming conventions. Also confirm whether you need the -min-age feature, as it requires understanding of release and tag timestamps.
Community notes