Reusable GitHub Actions
See Pipeline Conventions for constraints on how actions are written, tested, and structured.
Validates whether a given version string follows semantic versioning (semver) format.
Location: .github/actions/semver-validation
Usage:
- name: Validate version
id: semver
uses: loft-sh/github-actions/.github/actions/semver-validation@semver-validation/v1
with:
version: '1.2.3'
- name: Check if valid
run: echo "Valid: ${{ steps.semver.outputs.is_valid }}"Inputs:
version(required): Version string to validate
Outputs:
is_valid: Whether the version is valid semver (true/false)parsed_version: JSON object with parsed version componentserror_message: Error message if validation fails
See semver-validation README for detailed documentation.
Syncs Linear issues to the "Released" state when a GitHub release is published. Finds PRs between releases, extracts Linear issue IDs, and moves matching issues from "Ready for Release" to "Released".
Location: .github/actions/linear-release-sync
Usage:
- name: Sync Linear issues
uses: loft-sh/github-actions/.github/actions/linear-release-sync@linear-release-sync/v1
with:
release-tag: ${{ needs.publish.outputs.release_version }}
repo-name: my-repo
github-token: ${{ secrets.GH_ACCESS_TOKEN }}
linear-token: ${{ secrets.LINEAR_TOKEN }}See linear-release-sync README for detailed documentation.
Validates Renovate configuration files when they change in a pull request.
Location: .github/workflows/validate-renovate.yaml
Usage:
name: Validate Renovate Config
on:
pull_request:
jobs:
validate-renovate:
uses: loft-sh/github-actions/.github/workflows/validate-renovate.yaml@mainDetected config files: renovate.json, renovate.json5, .renovaterc, .renovaterc.json, .github/renovate.json, .github/renovate.json5.
Approves (and optionally enables auto-merge on) PRs from trusted bot accounts
whose title or branch matches a known safe pattern (chore: / fix(deps): /
backport/ / renovate/ / update-platform-version-). Hardened to never
block caller CI: continue-on-error: true on the job, every shell step
catches its own errors and exits 0, self-approval is pre-empted before calling
the external approve action.
Location: .github/workflows/auto-approve-bot-prs.yaml
Usage:
name: Auto-approve bot PRs
on:
pull_request:
types: [opened, synchronize]
jobs:
auto-approve:
permissions:
pull-requests: write
contents: read
uses: loft-sh/github-actions/.github/workflows/auto-approve-bot-prs.yaml@main
with:
trusted-authors: 'renovate[bot],loft-bot,github-actions[bot],dependabot[bot]'
auto-merge: false
secrets:
gh-access-token: ${{ secrets.GH_ACCESS_TOKEN }}gh-access-token must be a PAT whose identity differs from PR authors you want
to auto-approve (GitHub forbids self-review). When identity matches, the job
skips gracefully instead of failing.
End-to-end coverage: scenario-level e2e lives in vClusterLabs-Experiments/auto-approve-e2e. Runs weekly and on demand. Creates real PRs exercising every decision-table branch (chore/fix(deps) titles, backport/renovate/update-platform-version branches, ineligible titles) and asserts the never-hard-fail invariant.
Lints GitHub Actions workflow files using actionlint with reviewdog integration.
Location: .github/workflows/actionlint.yaml
Usage:
name: Actionlint
on:
pull_request:
jobs:
actionlint:
uses: loft-sh/github-actions/.github/workflows/actionlint.yaml@mainInputs:
reporter(optional, default:github-pr-review): reviewdog reporter type
Run all action tests locally:
make testRun tests for a specific action:
make test-semver-validation
make test-linear-pr-commenter
make test-linear-release-syncRun linters (actionlint + zizmor):
make lintSee all available targets:
make helpEach testable action has a dedicated workflow that runs its tests on PRs when the action's files change:
test-semver-validation.yaml- triggers on.github/actions/semver-validation/**test-linear-pr-commenter.yaml- triggers on.github/actions/linear-pr-commenter/**test-linear-release-sync.yaml- triggers on.github/actions/linear-release-sync/**release-linear-release-sync.yaml- builds and publishes the binary on tag push orworkflow_dispatch
Each reusable workflow (workflow_call) also has a smoke/integration test
workflow that triggers on PRs when the workflow file changes:
test-validate-renovate.yaml- callsvalidate-renovate.yamlwith local ref. Note: When triggered by workflow YAML changes alone, the innerpaths-filterwon't match any renovate config files sonpx renovate-config-validatornever runs. The validator only exercises its full path whenrenovate.jsonis also changed.test-detect-changes.yaml- callsdetect-changes.yamland asserts outputs (true/false)test-actionlint-workflow.yaml- callsactionlint.yamlwithgithub-pr-checkreporter (PR-only). Note:actionlint.yamlskips fork PRs silently; the verify job emits a warning when this happens.test-backport.yaml- callsbackport.yamland asserts the result isskippedtest-clean-github-cache.yaml- callsclean-github-cache.yaml(PR-only, since the underlying workflow needsgithub.event.pull_request.number)test-cleanup-backport-branches.yaml- callscleanup-backport-branches.yamlwithdry-run: truetest-conflict-check.yaml- callsconflict-check.yamland asserts success or skippedtest-claude-code-review.yaml- callsclaude-code-review.yamlto validate workflow is callabletest-claude.yaml- callsclaude.yamland assertsskipped(no@claudecomment event)test-notify-release.yaml- callsnotify-release.yamlwith dummy inputs to validate the contract
Post-merge, dispatch-integration-tests.yaml triggers full E2E tests in
vClusterLabs-Experiments/github-actions-test.
-
Node.js actions - add a
test/directory with Jest tests. Seesemver-validation/test/index.test.jsfor the pattern: spawn the action'sindex.jswithINPUT_*env vars and a tempGITHUB_OUTPUTfile, then assert on the parsed outputs. -
Go actions - add
*_test.gofiles next to the source. Seelinear-pr-commenter/src/main_test.go. Use standardgo test. -
Composite actions (YAML-only like
release-notification) - these delegate to third-party actions and have no local business logic to unit test. Validate their YAML structure through actionlint instead. -
Add a Makefile target for the new action following the existing pattern.
-
Add a CI workflow at
.github/workflows/test-<action-name>.yamlwith apathsfilter scoped to the action's directory.
The existing release-notification action uses a repository-wide tag:
git tag -f v1
git push origin v1 --forceReferenced as:
uses: loft-sh/github-actions/release-notification@v1For all new actions, we use action-specific tags for independent versioning:
# For the ci-notify-nightly-tests action
git tag -f ci-notify-nightly-tests/v1
git push origin ci-notify-nightly-tests/v1 --force
# For the semver-validation action
git tag -f semver-validation/v1
git push origin semver-validation/v1 --force
# For other actions, follow the same pattern
git tag -f action-name/v1
git push origin action-name/v1 --force# Reference actions using their specific tag
uses: loft-sh/github-actions/.github/actions/ci-notify-nightly-tests@ci-notify-nightly-tests/v1
uses: loft-sh/github-actions/.github/actions/semver-validation@semver-validation/v1