CI and release workflow
June 25, 2026 ยท View on GitHub
This repository includes these GitHub Actions workflows:
.github/workflows/ci.yml: runs checks on pull requests.github/workflows/release.yml: release-please-backed stable release workflow. On pushes tomain, it creates or updates a release PR from Conventional Commits; when that release PR is merged, it creates a draft semver GitHub release, publishes assets, publishes images, optionally publishes the VS Code extension, publishes the draft release, updates the GitHub Action floating tags, and then updates the Homebrew tap with:- Linux/Windows artifacts from Ubuntu (cross-compiled with
zig) - Darwin artifact from macOS (native arch)
- GHCR multi-arch image (
linux/amd64,linux/arm64) tagged with the release tag andlatest
- Linux/Windows artifacts from Ubuntu (cross-compiled with
.github/workflows/release-orchestration.yml: reusable workflow invoked byrelease.ymlandrolling.ymlto build release artifacts and publish GHCR images.github/workflows/rolling.yml: on merge tomain, publishes a rolling prerelease with Linux/Windows/Darwin build artifacts plus source bundle assets, updates GHCRrolling, and updates Homebrew tap formulalopper-rolling.github/workflows/docker-ghcr.yml: manual-only fallback to build/push the GHCR image on demand.github/workflows/memory-profiles.yml: scheduled/manual alloc-space profiling for the watched hotspot packages (dotnet,rust,analysis,golang) with uploaded artifacts and a workflow summary
Homebrew tap automation:
- The stable release workflow updates
ben-ranford/homebrew-tapFormula/lopper.rbfrom the new semver tag. - The rolling workflow updates
ben-ranford/homebrew-tapFormula/lopper-rolling.rbfrom the latest rolling tag. - The tap update step validates with
brew audit --strict --online,brew install --build-from-source, andbrew test lopperbefore push. - Set repository secret
HOMEBREW_TAP_TOKEN(with write access toben-ranford/homebrew-tap) to enable automatic tap updates.
Stable release automation:
- Release versioning and changelog generation are configured in
release-please-config.jsonand tracked in.release-please-manifest.json. - The Go project release notes are generated into the root
CHANGELOG.md; the VS Code extension keeps its ownextensions/vscode-lopper/CHANGELOG.md. - The current stable version is also synced into
extensions/vscode-lopper/package.jsonandextensions/vscode-lopper/package-lock.jsonby the release-please PR. - Manual
workflow_dispatchruns accept a release tag/version plus an optionalsource_sha, resolve the selected source ref, and create or reuse the draft GitHub release when the tag is not already present. - Release commits should use Conventional Commit prefixes:
fix:for patch releases,feat:for minor releases, and any type with a breaking-change marker (for examplefeat!:orfix!:) or aBREAKING CHANGE:footer for major releases. - Stable releases also publish the first-party action version refs. The release tag, such as
v1.7.0, is the exact action ref; the workflow force-updatesv1andv1.7to the same release commit for standard GitHub Actions major/minor pinning. - Set repository secret
RELEASE_PLEASE_TOKENto a PAT with contents and pull request write access so release-please-created PRs can trigger normal CI. If it is not configured, the workflow falls back toMAIN_SYNC_PATand thenGITHUB_TOKEN.
Example pipeline
Equivalent minimal job:
name: ci
on: [pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- run: sudo apt-get update && sudo apt-get install -y shellcheck
- run: make tools-install
- run: echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
- run: make ci
- run: make demos-check
os-smoke:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- run: make smoke
On pull requests, ci.yml also runs lopper delta analysis against the PR base and posts/updates a bot comment.
If SONAR_TOKEN is set, it additionally posts/updates a SonarQube summary comment.
When running locally with act, target the Linux-backed jobs explicitly (for example act pull_request -W .github/workflows/ci.yml --job verify), because Docker-based act environments do not provide hosted macOS runners.
First-party GitHub Action
Lopper ships the Scan with Lopper composite GitHub Action at the repository root. The action downloads a pinned Lopper release binary, builds lopper analyse arguments from named inputs, and supports the same CI outputs used by the CLI: threshold-gated analysis, JSON baselines, PR-comment markdown, and SARIF.
Pin both the action ref and the version input for fully reproducible CI. The default version: action uses the matching concrete action tag when the workflow references a full release tag such as v1.7.0; when the workflow references a floating action tag such as v1 or v1.7, it resolves the newest stable Lopper release in that series; otherwise it resolves the latest stable release. The action intentionally does not expose a raw extra-args passthrough, so shell values are never re-parsed by eval or another shell.
The standard release workflow publishes the semver action ref and updates the floating action refs with every stable release.
Pull request comment workflow
name: lopper-pr
on:
pull_request:
permissions:
contents: read
issues: write
pull-requests: write
jobs:
lopper:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Prepare base report workspace
env:
BASE_REF: ${{ github.event.pull_request.base.ref }}
run: |
mkdir -p .artifacts
git fetch --no-tags --depth=1 origin "${BASE_REF}"
git worktree add --detach .artifacts/base "origin/${BASE_REF}"
- name: Generate base report
uses: ben-ranford/lopper@v1.7.0
with:
version: v1.7.0
repo: .artifacts/base
language: all
top: '20'
format: json
output: .artifacts/lopper-base.json
- name: Remove base report workspace
if: ${{ always() }}
run: git worktree remove --force .artifacts/base || true
- name: Generate PR comment report
id: lopper
continue-on-error: true
uses: ben-ranford/lopper@v1.7.0
with:
version: v1.7.0
repo: .
language: all
top: '20'
baseline: .artifacts/lopper-base.json
format: pr-comment
output: .artifacts/lopper-pr-comment.md
threshold-fail-on-increase: '0'
enable-feature: reachability-vulnerability-prioritization-preview
advisory-source: security/lopper-advisories.yml
threshold-reachable-vulnerability-priority: high
- name: Upsert Lopper PR comment
if: ${{ always() }}
uses: actions/github-script@v9
with:
script: |
const fs = require('node:fs');
const marker = '<!-- lopper-pr-report -->';
const reportPath = '.artifacts/lopper-pr-comment.md';
const body = fs.existsSync(reportPath)
? `${marker}\n${fs.readFileSync(reportPath, 'utf8').trim()}`
: `${marker}\n## Lopper\n\nLopper did not produce a PR report.`;
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
per_page: 100,
});
const existing = comments.find(
(comment) =>
comment.user?.type === 'Bot' &&
typeof comment.body === 'string' &&
comment.body.includes(marker),
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
- name: Enforce Lopper threshold
if: ${{ steps.lopper.outcome == 'failure' }}
run: exit 1
advisory-source points at a local JSON or YAML file in the checked-out
workspace and requires
enable-feature: reachability-vulnerability-prioritization-preview. It does not
fetch a network vulnerability database. When
threshold-reachable-vulnerability-priority is set above off, Lopper fails on
reachable advisory findings at or above that priority; in baseline mode it gates
only on newly introduced reachable findings.
SARIF code-scanning workflow
name: lopper-code-scanning
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
security-events: write
jobs:
lopper:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Generate Lopper SARIF
uses: ben-ranford/lopper@v1.7.0
with:
version: v1.7.0
repo: .
language: all
top: '20'
scope-mode: repo
format: sarif
output: lopper.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: lopper.sarif
Make targets used by CI
make build: build local executable atbin/loppermake lint: rungolangci-lintwithgovet,unused,ineffassign,staticcheck,errcheck,gocritic,errorlint, andrevivemake actionlint: validate GitHub Actions workflow syntax and expressionsmake shellcheck: validate tracked shell scripts and git hooksmake mod-check: enforcego mod tidy -diffandgo mod verifymake dup-check: fail when new/changed Go lines exceed duplication max percentage versus base ref (defaults:DUPLICATION_MAX=3,DUPLICATION_TOKEN_THRESHOLD=55,DUPLICATION_BASE=origin/main)- Dup checker is pinned to immutable revision
DUPL_VERSION=f008fcf5e62793d38bda510ee37aab8b0c68e76c. make suppression-check: fail when staged, working-tree, or branch-added source lines introduce inline suppression markers such asnosonar,nosec,nolint,noqa,eslint-disable,ts-ignore,ts-expect-error, and coverage-bypass commentsmake format-check: fail ifgofmtchanges are neededmake security: rungosecmake vuln-check: rungovulncheckmake test-leaks: rungo test ./...withgoleakenabled to catch leaked goroutinesmake test-race: rungo test -race ./...make bench-mem: run the curated memory benchmark suite with-benchmemmake bench-gate: compare curated memory benchmark deltas against a base ref (defaults:MEMORY_BENCH_BASE=origin/main,MEMORY_BENCH_MAX_BYTES_PCT=15,MEMORY_BENCH_MAX_ALLOCS_PCT=10)make cov: run tests with coverage profile and enforce both minimum total coverage (COVERAGE_MIN, default98) and minimum per-package coverage (COVERAGE_PACKAGE_MIN, default98), excluding helper-only packages such asinternal/testutil,internal/testsupport, and the local CI helper tooltools/benchdeltamake smoke: run cross-OS smoke checks (mod-check + test-race + build)make ci:format-check + mod-check + lint + actionlint + shellcheck + dup-check + suppression-check + security + vuln-check + test + test-leaks + test-race + bench-gate + build + covmake mem-profiles: capture package-focused alloc-space summaries for the watched hotspot packages and write them under.artifacts/memory-profiles/make toolchain-check: verify required cross toolchain binaries plusshellcheckmake toolchain-install: install required OS toolchains (go,zig,shellcheck) on macOS/Linuxmake tools-install: install pinned Go-based CI tools locally (golangci-lint,gostyle,gosec,actionlint,govulncheck)make setup: bootstrap toolchain + module download + readiness checksmake release VERSION=<tag>: build release archives indist/(host platform by default)
Coverage artifacts:
.artifacts/coverage.out:go testcoverage profile.artifacts/coverage-total.txt: total percentage used by CI gating.artifacts/coverage-packages.txt: per-package coverage percentages emitted by the coverage gate.artifacts/coverage-package-failures.txt: packages that fell below the per-package minimum, formatted for CI comments.artifacts/bench-base.out: benchmark output captured from the base ref.artifacts/bench-head.out: benchmark output captured from the current ref.artifacts/memory-bench-summary.md: markdown summary posted to PRs.artifacts/memory-bench-status.txt: exit status from the memory benchmark gate (0pass,1regression,2execution/parsing error).artifacts/memory-profiles/<timestamp>/: watched-package alloc-space summaries, raw profiles, and logs
Memory benchmark approval:
- PR CI runs the memory benchmark delta gate in report-only mode and comments the summary on the PR.
- Regressions are still blocked unless a maintainer applies the
memory-approvedlabel. - Newly added benchmarks are reported but not gated until the same benchmark name exists on both the base and head refs, which keeps first-rollout noise manageable.
On pull requests, if the coverage gate fails, CI posts/updates a PR comment with total coverage and any packages that fell below the per-package minimum.
The 98% minimum is intentionally enforced in both Makefile and CI workflow config for the combined gate and the per-package gate to keep local and CI behavior aligned.
Cross-compilation uses zig cc for CGO targets.
Current cross-CGO release targets are Linux and Windows.
For Darwin artifacts, build on a macOS runner with native architecture.
To build multiple platforms:
make release VERSION=vX.Y.Z PLATFORMS="linux/amd64 darwin/arm64 windows/amd64"
Install toolchain support with:
make toolchain-install