Guidance for AI coding agents working in this repository.
smallstep/workflows holds the reusable GitHub Actions workflows that every Smallstep Go repository calls from its own .github/workflows/*.yml, plus the shared .golangci.yml that those repositories fetch at lint time and a .versions file that pins the org-wide Go toolchain. The repo is almost entirely YAML and shell; the only Go is lintapp/, a dummy module (github.com/smallstep/workflows/lintapp) that exists so CI can prove the shared lint config still works. There are no tags or releases: callers reference @main, so a merge to main takes effect in every consuming repository immediately.
There is no Makefile. What CI runs (.github/workflows/ci.yml) can be reproduced locally:
actionlint -color # lint every workflow file (.github/actionlint.yml holds the ignores)
zizmor --min-severity medium --min-confidence medium . # security audit of the workflows (.github/zizmor.yml holds the ignores)
golangci-lint config verify --config=.golangci.yml # validate the shared lint config (needs the golangci-lint pinned in .versions)
(cd lintapp && golangci-lint run --config=../.golangci.yml) # lint the dummy app with the shared config
(cd lintapp && go test ./...) # test the dummy app
.github/scripts/generate-versions --no-refresh # rebuild .versions from .versions.json; CI fails if the result differs from what is committed
.github/scripts/generate-versions # refresh .versions.json from upstream (needs gh, jq, curl, tar, go); normally run only by versions.ymlgolangci-lint config verify and the lintapp lint need a golangci-lint built with a Go at least as new as the go line in .versions; an older local binary refuses to load the config. Do not run go build inside lintapp/: it drops an untracked lintapp/lintapp binary.
| File | Generator |
|---|---|
.versions |
.github/scripts/generate-versions, run nightly by versions.yml, which opens a PR on the ci/versions branch. Edit .versions.json and regenerate; never hand-edit .versions. |
.github/workflows/
├── goCI.yml # umbrella: fans out to goLint, goTest, goBuild, govulncheck, codeql-analysis
├── goLint.yml # golangci-lint (+ optional go mod tidy check, go generate drift check)
├── goTest.yml # gotestsum + optional codecov, Go stable/oldstable matrix
├── goBuild.yml # runs the caller's build command on the Go matrix
├── govulncheck.yml # govulncheck ./...
├── codeql-analysis.yml # CodeQL for Go
├── code-scan.yml # thin wrapper around codeql-analysis for nightly cron callers
├── actionci.yml # umbrella: actionlint + zizmor (+ a no-op frizbee job kept for compatibility)
├── actionlint.yml # actionlint via the pinned docker image
├── zizmor.yml # zizmor; uploads SARIF to Advanced Security on public repos by default
├── frizbee.yml # deprecated, echoes and exits 0
├── goreleaser.yml # GoReleaser Pro release; optional GPG signing and package-repo upload
├── docker-buildx-push.yml # multi-platform buildx push + cosign
├── dependabot-auto-merge.yml# enables auto-merge on Dependabot PRs
├── triage.yml # labels PRs "needs triage", adds to the OSS triage project
├── ci.yml # THIS repo's CI (not reusable)
├── versions.yml # THIS repo's nightly .versions sync (not reusable)
└── sync-winget-fork.yml # THIS repo's monthly winget-pkgs fork sync (not reusable)
.github/actions/versions/ # composite action exposing .versions as step outputs (see its README.md)
.github/scripts/generate-versions
.golangci.yml # shared lint config consumed remotely (see below)
.versions / .versions.json # pinned toolchain: go, golangci-lint, gotestsum, govulncheck, goimports, gopls, air, mirrord
lintapp/ # dummy Go module used only to exercise .golangci.yml in CI
All reusable workflows are on: workflow_call. Callers look like:
jobs:
ci:
permissions: { actions: read, contents: read, security-events: write }
uses: smallstep/workflows/.github/workflows/goCI.yml@main
with:
only-latest-golang: false
run-codeql: true
secrets: inherit- goCI.yml - inputs:
run-lint,run-test,run-build,run-govulncheck,run-codeql(default false),run-codecov,only-latest-golang(true:stableonly; false: addsoldstable),build-command(V=1 make build),test-command(gotestsum with coverage),golangci-lint-args(--timeout=30m),golangci-lint-version/gotestsum-version/govulncheck-version(empty means take it from.versions),goprivate,os-dependenciesand per-job<job>-os-dependencies,runs-onand per-job<job>-runs-on,setup-bats,lint-skip-go-generate(default false),lint-skip-go-mod-tidy(default true),codeql-build-cmd,codeql-build-mode,codeql-make-bootstrap. Secrets:SSH_PRIVATE_KEY,PAT,CODECOV_TOKEN. - goLint.yml - if the caller has no
.golangci.*file, it downloads this repo's.golangci.ymlat the calling SHA and passes--config. Withskip-go-generate: false(the default) it deletes every// Code generated ... DO NOT EDIT.file except*.pb.go, runsgo generate ./..., and fails on any diff.skip-go-mod-tidy: falseaddsgo mod tidy -diff. - goTest.yml / goBuild.yml - same Go matrix logic; both
evalthe caller's command string.goTestsetsGOTESTSUM_JSONFILEand annotates failures. - actionci.yml - inputs:
run-actionlint,run-zizmor,run-frizbee(all default true),zizmor-advanced-security(string; empty auto-enables on public repos). The calling job must grantactions: readandsecurity-events: writeor the zizmor job fails at startup. - code-scan.yml - inputs:
run-codeql,runs-on,codeql-build-cmd,codeql-build-mode,codeql-os-dependencies,os-dependencies. Secrets:PAT,SSH_PRIVATE_KEY. - goreleaser.yml - inputs:
go-version(stable),cosign-version,is-prerelease(default true),enable-gpg-sign,enable-packages-upload,os-dependencies,runs-on,goprivate. Required secrets:GORELEASER_PAT,GORELEASER_KEY,AWS_S3_REGION; the rest are optional. Runs GoReleaser Prorelease --clean. - docker-buildx-push.yml - required inputs
platforms,tags,docker_image; optionaldocker_file,docker_build_args,runs_on(note the underscore). Required secretsDOCKER_USERNAME,DOCKER_PASSWORD. Signs the pushed digest with cosign. - dependabot-auto-merge.yml - required secret
DEPENDABOT_TOKEN; only acts when the PR author isdependabot[bot]. - triage.yml - inputs
run-label-pr,run-add-to-oss-triage-project; secretTRIAGE_PAT. Expects apull_request_targetcaller for the label job. - .github/actions/versions - composite action; outputs one value per
.versionskey (without thego:prefix, no leadingv) plus__json.
- Pin every third-party action to a commit SHA with a
# vX.Y.Zcomment. zizmor enforces this;.github/zizmor.ymlonly relaxes rules for the reasons stated in its comments. - Reusable workflows fetch
.versionsand.golangci.ymlfrom their own commit viajob.workflow_repository/job.workflow_sha, so a caller pinned to a SHA gets the pins from that SHA. actionlint does not know those properties; the ignore lives in.github/actionlint.yml. - Workflow-level
permissions: contents: readis the floor; jobs that need more declare it. A reusable workflow can never be granted more than its caller grants, so document any extra permission a caller must add (see theNOTE(@azazeal)comments ingoCI.ymlandactionci.yml). - Shell steps use
set -euo pipefailand pass inputs throughenv:rather than interpolating${{ }}intorun:(zizmor's template-injection check). - Caller-supplied command strings (
build-command,test-command,codeql-build-cmd) areevaled on purpose. - Formatting:
.editorconfigsays tabs for Go, two-space indents for YAML and JSON, four spaces for.github/scripts/*, LF endings everywhere. - Go toolchain moves are gated:
goin.versionsonly advances when golangci-lint can lint it,actions/setup-gocan install it, and Docker Hub has the image, and only after a 7-day cooldown. Do not bumpgoby hand.
Consuming repositories do not vendor this file. Their make lint targets run golangci-lint run --config <(curl -s https://github.com/@raw/smallstep/workflows/main/.golangci.yml) (some still say master; GitHub redirects it), and goLint.yml does the equivalent for any caller without a local config. A change here therefore alters lint results across the org on the next run, including on PRs that touched nothing. When editing it:
- Keep only settings that differ from golangci-lint defaults (the file header says so).
- Verify with the pinned golangci-lint:
golangci-lint config verify --config=.golangci.yml, then lintlintapp/with it. - Adding a linter or removing an exclusion should be tried against at least one large consuming repository before merging.
gomoddirectives.replace-allow-listis the org-wide list of permittedreplacedirectives; extend it rather than adding//nolintin callers.
CI in this repo only proves the files parse, the pins resolve, and the dummy app lints. To exercise a reusable workflow for real, push a branch here and point a consuming repository's caller at it temporarily:
uses: smallstep/workflows/.github/workflows/goCI.yml@my-branchOpen a PR in the consuming repo, watch the run, then restore @main before merging either side. Public consumers you can use for this include smallstep/cli, smallstep/certificates and smallstep/crypto. Remember that the moment the branch here merges, every @main caller picks it up.