Repository navigation
GitHub Action
axnic/rtunk is a GitHub Action that installs rtunk, verifies it, caches its tools and plugins,
runs rtunk check and reports the findings as annotations and a job summary. It is rtunk's
counterpart of trunk-io/trunk-action, on Linux and macOS runners. It is a composite action
defined by action.yml at the root of the repository.
Once a release has been published with "Publish this Release to the GitHub Marketplace" ticked, the action is
listed on the GitHub Marketplace as rtunk check (the name rtunk is taken by a GitHub user). The listing name
does not change how the action is referenced: it stays uses: axnic/rtunk@<tag>.
name: rtunk
on: pull_request
permissions: {}
jobs:
rtunk:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@<sha> # pin by full SHA
with:
fetch-depth: 0 # changed-since-base needs the base commit
- uses: axnic/rtunk@v0.14.0
with:
version: v0.14.0
check-mode: changed-since-base
require-attestation: trueThe repository must have an rtunk configuration (.rtunk/rtunk.yaml, or an existing
.trunk/trunk.yaml): without a .rtunk or .trunk ancestor, rtunk check refuses to run and the
job fails.
-
Pin the action.
uses: axnic/rtunk@v0.14.0pins by tag. Tags can be moved, so a workflow that must be immutable pins the commit instead, with the tag in a comment:- uses: axnic/rtunk@2e4dd9dc340083cf8d318e576ef0114b65a398e0 # v0.14.0
-
Pin rtunk itself. The action version and the rtunk version are independent:
uses:selects the code of the action,version:selects the rtunk release it installs, andversiondefaults tolatest. Set both so a new release never changes your pipeline unannounced. -
Grant
contents: read. It is the only permission the action asks for: the cache, the annotations and the summary do not use theGITHUB_TOKEN. Start frompermissions: {}at the top of the workflow and grant it to the job. -
Check out with enough history when
check-modeischanged-since-base:actions/checkoutwithfetch-depth: 0(see Choose which files are checked). -
Use a Linux or macOS runner (
amd64orarm64). The runner needs theghCLI, present on GitHub-hosted runners.
Defaults are those of action.yml.
| Input | Default | Meaning |
|---|---|---|
version |
latest |
latest (newest published release), a tag such as v0.14.0, or source
|
check-mode |
all |
all or changed-since-base
|
arguments |
empty | Extra arguments for rtunk check, split on spaces with no quoting, e.g. --filter=shellcheck --exclude=a,b
|
working-directory |
. |
Directory rtunk runs in, relative to the workspace |
require-attestation |
false |
Fail when the release has no signature bundle to verify, instead of falling back to the checksum alone |
token |
github.token |
Token the gh CLI uses to resolve, download and verify the release |
| Output | Meaning |
|---|---|
version |
The version that ran: a tag, or source
|
exit-code |
Exit code of rtunk check: 0 clean, 1 findings or error |
The action sets --format github itself (see Annotations and job summary),
so arguments must not contain --format.
-
allrunsrtunk check .: every file underworking-directorythat git tracks or could track. -
changed-since-baserunsrtunk check --from <base>, where<base>is the base commit of the pull request (pull_request,pull_request_target), of the merge-queue entry (merge_group), or the previous head of a push (push). On any other event, or for the first push of a branch (an all-zero previous head), there is no base and rtunk checks the changes sinceHEAD(see Checking code).
Warning
changed-since-base needs the base commit in the checkout: use actions/checkout with
fetch-depth: 0. When the base is missing, the action tries to fetch it (unshallowing a shallow
clone), then fails with an error that says so.
On schedule and workflow_dispatch there is no base: use check-mode: all, the default.
version |
What runs |
|---|---|
latest |
The tag of the newest published release (gh api repos/axnic/rtunk/releases/latest) |
vX.Y.Z |
That release (a semver prerelease suffix such as v1.0.0-rc.1 is accepted) |
source |
rtunk built with go build from the action's own checkout (Go is set up for you) |
Any other value fails the job. latest moves with every release: pin an exact tag for
reproducible runs. Use source only in a repository that hosts rtunk, to check itself with the
code under review; this repository's own CI does exactly that, and source verifies nothing since
nothing is downloaded.
v0.15.0 is the oldest supported release: it is the first with --format github, which the action
always passes. source and latest are fine. Pinning an older release installs it (releases before
v0.14.0 are unsigned, see below, and declare the previous Go module path), but rtunk check then
fails on the unknown --format flag.
With a release version, the action downloads rtunk-<tag>-<os>-<arch>.tar.gz and checksums.txt
and verifies the archive as described in SECURITY.md:
- The archive matches
checksums.txt. - When the release carries
checksums.txt.sigstore.json,gh attestation verifychecks the archive's SLSA build provenance againstaxnic/rtunk, andcosign verify-blobchecks the signature ofchecksums.txtagainst the Release workflow's identity whencosignis onPATH. Any failure fails the job.
Two Release workflow identities are accepted, so pinned older versions and latest both verify:
rtunk's own workflow_dispatch.release.yaml (releases cut before the move to the central
workflows) and the central go.publish.yaml of axnic/.github (later releases). Both are
anchored on refs/heads/main, the OIDC issuer is GitHub Actions', and the signing repository is
pinned to axnic/rtunk. See SECURITY.md for the exact patterns.
v0.14.0 is the first release signed with cosign and carrying SBOMs and SLSA provenance, so every
release from v0.14.0 on has a bundle to verify. Releases up to v0.13.2 have none: the action
warns (rtunk <tag> is not signed: verified against checksums.txt only) and relies on the checksum,
which only detects a corrupted download, not a tampered release.
Tip
For v0.14.0 and later, set require-attestation: true. A release without a bundle is then
refused instead of accepted with a warning, so a release stripped of its signature cannot slip
through. The input only decides what happens when the bundle is absent; a bundle that is present
is always verified.
Hosted runners do not necessarily ship cosign. To also check the signature of checksums.txt,
install it before the action, for example with sigstore/cosign-installer; the attestation check
runs either way.
The action restores and saves downloads/, plugins/ and registry/ of
rtunk's cache (~/.cache/rtunk, set through
RTUNK_CACHE_DIR) with actions/cache. Run logs are not cached.
| Part | Value |
|---|---|
| Key | rtunk-v1-<os>-<arch>-<rtunk version>-<hash of the config> |
| Restore key |
rtunk-v1-<os>-<arch>-<rtunk version>-: the newest cache of the same rtunk version |
| Config hash |
.rtunk/*.yaml, .rtunk/*.lock, .trunk/*.yaml and rtunk.lock of the project root |
The project root is the nearest ancestor of working-directory holding .rtunk or .trunk. With
no config file the hash is no-config. For version: source, the rtunk version is a hash of the
built binary. The cache is saved with if: always(), so a run whose checks fail still keeps the
tools it downloaded. An exact hit is not saved again (cache keys are immutable). Linters that a
restore-key hit does not cover are downloaded and saved under the new key.
Cache entries follow GitHub's scoping rules: a pull request reads caches saved on its base branch
and saves its own under its merge ref, so a run on main (a push, or a nightly job) is what warms
the cache for later pull requests.
The action runs rtunk check --format github and rtunk itself writes the report: annotations on
stdout and the job summary in GITHUB_STEP_SUMMARY. The action does no post-processing. The flag is
always passed, so v0.15.0 is the minimum supported release (see
Choose the rtunk version).
The --format github output is described in
Checking code: one annotation per finding on its file
and line, titled <linter>/<rule> (error for high, warning for medium, notice for the
rest), an error annotation for a linter that failed to run and a warning for a skipped one. On a
pull request, annotations appear in the Files changed tab and the checks list.
rtunk emits every finding, but GitHub displays only the first 10 errors and 10 warnings per step, and 50 annotations per job. The rest appear in the step log only, so a run with more findings than that shows fewer annotations than the log lists.
The job summary (rtunk check) holds the totals, counts per severity and per linter, a table of the
first 50 findings with an N more not shown line, and the failed and skipped linters. rtunk appends
it to the file named by GITHUB_STEP_SUMMARY, which GitHub sets in every step.
The job fails in the last step, after the cache is saved and the results reported, with the exit
code of rtunk (see exit codes). The exit-code output exposes
it to later steps.
The common setup: check what the pull request changes, and what the merge queue is about to merge.
name: rtunk
on:
pull_request:
merge_group:
permissions: {}
jobs:
rtunk:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@<sha>
with:
fetch-depth: 0
- uses: axnic/rtunk@v0.14.0
with:
version: v0.14.0
check-mode: changed-since-base
require-attestation: trueA scheduled run has no base. It checks the whole tree and keeps the cache warm for pull requests.
name: rtunk nightly
on:
schedule:
- cron: 0 3 * * *
workflow_dispatch:
permissions: {}
jobs:
rtunk:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@<sha>
- uses: axnic/rtunk@v0.14.0
with:
version: v0.14.0working-directory selects where rtunk runs. The configuration is the nearest .rtunk or .trunk
above that directory, up to the workspace root, and the cache key hashes that configuration.
jobs:
rtunk:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
project: [services/api, services/web]
permissions:
contents: read
steps:
- uses: actions/checkout@<sha>
with:
fetch-depth: 0
- uses: axnic/rtunk@v0.14.0
with:
version: v0.14.0
working-directory: ${{ matrix.project }}
check-mode: changed-since-baseLegs that resolve to the same project root share one cache key.
arguments forwards flags to rtunk check, here to run two linters and the security checks only.
- uses: axnic/rtunk@v0.14.0
id: rtunk
with:
version: v0.14.0
arguments: --filter=shellcheck,yamllint --security-only-
Pull requests from forks run with a read-only
GITHUB_TOKENand no secrets. That is enough: the action only reads the release, and annotations and the summary are workflow commands, not API calls. Use thepull_requesttrigger. Withpull_request_target, the workflow runs with the base repository's privileges, and rtunk executes the linters and plugin sources that the checked out configuration declares: never check out and run untrusted pull request code under that trigger. -
Private repositories work the same way: the action downloads rtunk from the public
axnic/rtunkreleases and never sends your code anywhere. It configures no credentials for private plugin sources; aplugins.sourcesentry that rtunk cannot clone with the checkout's credentials fails the run. -
Self-hosted runners must provide
gh, run Linux or macOS, and beamd64orarm64.
| Message or symptom | Cause and fix |
|---|---|
rtunk supports Linux and macOS runners only |
Windows runner. Use ubuntu-latest or macos-latest. |
the gh CLI is required to ... |
The runner has no gh. Install it, or use a GitHub-hosted image. |
no published rtunk release found; pin a version or use version: source |
latest could not be resolved: no non-draft release, or the token cannot read it. Pin version: vX.Y.Z. |
version must be 'latest', 'source' or a tag like v0.13.2 |
Malformed version. Use the tag with its v prefix, as v0.14.0. |
base commit <sha> is not in the checkout; use actions/checkout with ... |
Shallow checkout with changed-since-base. Set fetch-depth: 0 on actions/checkout. |
rtunk <tag> has no signature bundle and require-attestation is true |
The release predates signing (up to v0.13.2). Move to v0.14.0 or later, or set require-attestation: false. |
rtunk <tag> is not signed: verified against checksums.txt only |
Warning for a release up to v0.13.2. Pin a newer version and set require-attestation: true. |
gh attestation verify or cosign verify-blob fails |
The archive or checksums.txt was not produced by the Release workflow. Do not use it; report it per SECURITY.md. |
check-mode must be 'all' or 'changed-since-base' |
Typo in check-mode. |
Job fails with rtunk check exited with code 1, no annotation |
The config is invalid, or no .rtunk/.trunk was found above working-directory. Read the step log; a failed linter is an annotation and in the summary's Failed linters list. |
unknown flag: --format (or similar), job fails |
The pinned release predates v0.15.0, the first with --format github. Pin v0.15.0 or later, or use latest or source. |
| Green job, nothing checked | No file was selected. With changed-since-base, no relevant file changed; on schedule use all. |
| Fewer annotations than findings | GitHub shows at most 10 errors and 10 warnings per step and 50 annotations per job. The step log lists all; the summary lists the first 50. |
| Every run downloads the linters again | The cache key changed (config or rtunk version) or the run is on a new branch with no base-branch cache. Warm it with a run on main. |
- Windows runners are not supported, like rtunk itself.
-
argumentsis split on spaces; an argument containing a space cannot be passed. -
tokenis used by theghCLI only: it does not authenticate plugin source clones.
- Checking code — flags, file selection and exit codes
- Cache and logs — what the cache holds
- Installation — installing rtunk outside CI
- SECURITY.md — verify a release by hand
Home · FAQ · Report an issue ·
Source of this page: docs/ in the rtunk repository
Getting started
Using rtunk
- Checking code
- Formatting code
- Ignoring issues
- Managing linters
- Actions and git hooks
- Keeping tools up to date
- Cache and logs
- GitHub Action
Reference
Internals