CLI tool
suzuki-shunsuke/pinact avatar
suzuki-shunsuke/pinact

pinact leaves branch references unpinned by default, and compiles its documentation into the binary

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.

1,255 stars43 forksGoMIT

At a glance

What is it?
pinact rewrites GitHub workflow and composite action files so every action is referenced by full commit SHA, checks that in CI, and reports findings as SARIF. Two choices in it are more interesting than the feature list suggests: a branch reference is left alone unless you opt in, and the documentation is a build artefact served by the binary itself, which is why a pinned older binary has no doc commands at all.
Who is it for?
pinact fits a repository that wants action references to be immutable and wants the same check enforced in CI rather than by convention. Four things to know before you wire it in.
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 received new commits within the last day.
What is it written in?
Mainly Go, according to GitHub's language statistics.

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

From daily snapshots

Star history

  • suzuki-shunsuke/pinact1,255 ★ · +107
Star history
Repository2026-07-282026-11-14
suzuki-shunsuke/pinact1.1k1.3k

107 Stars in the last 109 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

A branch reference is left alone by default, and there is a flag to change that

The central security argument on the page is that tags are mutable, so referencing an action by tag carries a real risk, and that pinning to a full length commit SHA is currently the only way to use an action as an immutable release. Having taken that position, the tool does not apply it everywhere. A branch reference is not pinned by default, and there is a whole document given over to explaining why, with its own title asking why pinact does not pin some actions. The opt-in is a single flag described as opting a branch reference in to being pinned. That is the piece to understand before you run the check in CI: a workflow that follows a branch will pass, and pass silently, because the tool considers that correct rather than because it missed anything.

The documentation is a build artefact, and the commands that serve it start at version 5

The documents under the docs directory are compiled into the executable, and the binary serves them. Two subcommands do it, one listing every document with a description as JSON and the other printing a single document, with a document in a subdirectory named by its path.

sh
pinact docs list # The name and the description of every document, as JSON
pinact docs show config # One document
pinact docs show codes/005 # A document in a subdirectory is named by its path

The parenthetical that matters is the version floor: those commands exist from 5.0.0 onwards. A project that pins an older binary, in a workflow or a prebuilt image, has no way to ask that binary what it does. The page also notes that the command help document is generated from the CLI itself rather than written by hand, and that the README, the embedded documents and the coding-agent skill are described as one source with no duplicated maintenance.

The docs are indexed by numbered failure, including one schema version that was abandoned

Instead of a troubleshooting page, the documentation set is a numbered list of specific things the tool refuses to do, and reading the list is more informative than any single entry. One covers why a version comment is not necessarily true and how the comment verification flag checks it. Two covers a missing schema version. Three covers a version that was abandoned, which tells you a configuration schema was released and then withdrawn. Four covers an unsupported configuration format version. Five covers the requirement that a SHA-pinned action carry a version comment, including why a bare SHA is rejected and how to resolve it. A separate document lists the old schema versions the tool no longer supports, and two upgrade guides cover the major version transitions, which is how you know a v5 install needs a v3-era configuration migrated twice.

Three different flags all mean check instead of fix

The everyday invocation pins whatever the repository contains, with no argument, and the tool works out which files to touch. The checking story is where the flags pile up, and there are three of them for what sounds like one job. A check flag makes the run exit with a non-zero code when something needs pinning rather than editing anything. A fix flag set to false does the same thing by a different route. And an offline check disables the API calls entirely, which matters because pinning resolves tags through GitHub and that is where rate limits bite.

sh
pinact run --check

Exit codes are documented as a set of four, zero through three, each with a stated meaning, so a CI failure tells you which kind of problem you have rather than just that something is wrong.

The worked example at the top of the README ends in the middle of a path

The page opens with a diff, which is the right choice for a tool that rewrites files. The first entries show what a run actually does: an action already pinned to a SHA whose comment is one minor version behind gets the comment brought forward, an action pinned to a mutable tag gets rewritten to a SHA with a full version comment added, and an action pinned to a SHA with a stale comment has its comment refreshed to the real release. Then the example stops partway through a line, in the middle of a path for an action on a different repository, with no closing context and no indication of what was meant to follow. The three cases above the break are the useful part, since they show that comments are rewritten rather than trusted.

A version comment is written and then verified, because the comment is not evidence

There is a documented rule that a SHA-pinned action must carry a version comment, and a bare SHA is rejected. The interesting part is the reason, given in the documentation title: a version comment is not necessarily true. A comment is a human-readable assertion next to a hash that is the only thing the runner actually resolves, and nothing stops the two from drifting apart. So the tool treats the comment as a claim to be maintained rather than as a fact, rewrites it when it knows the real release, and offers a flag that verifies it against the API. That is a stricter contract than most linters impose, and it is why the upgrade path from a repository that pinned without comments is documented at all: existing files fail the check until someone adds the missing text.

The token has three sources, and the dependency list carries two YAML libraries

GitHub access is needed for anything that resolves a tag, and the page offers it three ways: an environment variable read from the shell, the operating system keyring, or a helper that manages the credential. The dependency list explains the keyring half, with separate libraries for Windows credentials, a desktop bus service, and the cross-platform keyring wrapper, plus two first-party modules for the device flow and for revoking a token. The two YAML libraries are the other detail worth naming. One is the strict parser, the other is a faster implementation, and a workflow-editing tool wants the parser that round-trips a file with its comments and formatting intact rather than the one that reads fastest. A filesystem abstraction sits in the direct dependencies too, which is what allows the same code path to run against a fixture tree instead of a real checkout.

The repository root carries a dozen tool configs and a pinact config for itself

The tree is wider than a single-binary CLI usually needs. Alongside the source and the docs there are two linter configs, a release config, a Renovate config in a JavaScript dialect rather than JSON, a typos dictionary, a link checker config, a task runner config, a Pkl file, a rules directory for one particular coding tool, and two agent instruction files. There is also a configuration file for pinact itself, so the project dogfoods its own tool, and a JSON schema directory plus a schema generation dependency, which is how the documented schema for the config file gets validated. A test data directory and a specifications directory sit alongside them. None of this is a problem, but it does mean the maintenance surface is wider than the two commands most users run.

Editorial conclusion

pinact fits a repository that wants action references to be immutable and wants the same check enforced in CI rather than by convention. Four things to know before you wire it in. A branch reference is not pinned by default, so a workflow pointing at a branch will pass your check untouched. The embedded documentation commands only exist from version 5, so a pinned older binary cannot answer questions about itself. The minimum release age exists as a guard against pulling a freshly cut tag, which is the right default. And if you adopt it across many repositories, the flag that pins only changed lines is the one that makes the rollout survivable.

Frequently asked questions

Why does pinact leave a branch reference alone?

A branch reference is not pinned by default, and a whole document is given over to explaining why. There is a branch-to-tag flag that opts a branch reference in to being pinned instead.

How do I check a repository in CI without changing any files?

Run the check flag, which exits with a non-zero code when something needs pinning. The same behaviour is reachable through a fix flag set to false, and an offline check flag disables the API calls as well, which helps with rate limits.

Where does pinact keep its documentation?

The documents are embedded in the binary and served by two subcommands, one listing every document as JSON and one printing a single document by name, with a path used for documents in subdirectories. Those commands exist from version 5.0.0.

Does pinact verify the version comment next to a pinned SHA?

It can. The documentation states a version comment is not necessarily true, so the tool maintains it when it knows the real release and offers a flag to verify it against the API. A SHA-pinned action with no comment at all is rejected.

How does pinact get a GitHub token?

From an exported environment variable, from the operating system keyring, or from a credential helper. The documentation names the environment variable, the keyring integration and the helper as the three supported routes.

Official sources

  1. Official README
  2. Project repository
  3. Release notes
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/suzuki-shunsuke-pinact.svg)](https://hysenlabs.com/projects/suzuki-shunsuke-pinact)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/suzuki-shunsuke-pinact.svg)](https://hysenlabs.com/projects/suzuki-shunsuke-pinact)

Was this useful?