About go-ci
go-ci is a skill for Go maintainers that outlines a CI pipeline covering builds, vetting and linting, race-enabled tests, and vulnerability scanning. It includes GitHub Actions configuration, local CI parity, and repository security practices.
Key features
- 01Four core CI gates
- 02GitHub Actions workflow guidance
- 03golangci-lint configuration
- 04Race testing and coverage gates
- 05Security scanning and dependency updates
Use cases
- 01Setting up a Go CI pipeline
- 02Aligning local checks with CI
- 03Adding race and coverage checks
- 04Hardening repository automation
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ eduardo-sl/ go-agent-skills --skill go-ciAfter installing, type/go-ciin Claude Code to confirm it loaded.
Bundled filesreferences/security-and-automation.md
Details
From the project READMEGo CI
A Go pipeline has exactly four gates: build, vet/lint, test with race detector, vulnerability scan. Everything else is optimization.
Detailed reference material, loaded on demand:
references/security-and-automation.md— CodeQL and gosec SARIF, Dependabot config, SHA pinning, repository hardening, Trivy, an AI review workflow.
Read a reference file only when the summary below is not enough.
1. Baseline GitHub Actions Workflow
name: ci
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod # single source of truth
cache: true # caches module + build cache
- run: go build ./...
- run: go vet ./...
- run: go test -race -shuffle=on -coverprofile=coverage.out ./...
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
- uses: golangci/golangci-lint-action@v9 # v7+ runs golangci-lint v2
with:
version: v2.6 # pin a minor; v2.6+ has `modernize`
vuln:
runs-on: ubuntu-latest
steps:
- uses: golang/govulncheck-action@v1
with:
go-version-file: go.mod
Action major versions move several times a year. Treat the ones above as
a snapshot, and let Dependabot's github-actions ecosystem keep them
current rather than editing them by hand.
Key decisions baked in:
go-version-file: go.mod— never hardcode the Go version in two places.cache: trueon setup-go handles module and build caches; do not add manualactions/cachesteps for Go on top of it.-race -shuffle=on— races and order-dependent tests fail in CI, not production.permissions: contents: read— least privilege by default.- Lint in a separate job — it fails fast and parallelizes with tests.
2. golangci-lint Configuration
Commit a .golangci.yml; an unconfigured linter is noise:
version: "2" # required: golangci-lint v2 rejects v1 files
linters:
default: standard # errcheck, govet, ineffassign, staticcheck, unused
enable:
- errorlint # %w misuse, == on errors
- gosec # security patterns
- revive # style, replaces golint
- misspell
exclusions:
rules:
- path: _test\.go
linters: [gosec] # test code may use weak randomness etc.
golangci-lint v2 moved exclusions from issues.exclude-rules to
linters.exclusions.rules and made the version key mandatory. A v1 file
fails to load, and golangci-lint migrate converts one in place.
Start from this small set and add linters deliberately. Enabling
everything produces hundreds of findings nobody triages. nolint
directives require a reason: //nolint:gosec // G404: jitter, not crypto.
3. Build Matrix — Only When You Ship It
strategy:
matrix:
go: [oldstable, stable] # the two releases the Go team supports
os: [ubuntu-latest, macos-latest, windows-latest]
Libraries: test the two supported Go releases. setup-go resolves
stable/oldstable at run time, so the matrix never goes stale.
Services deployed on Linux: skip the OS matrix — it doubles cost for
platforms you never ship. Cross-compilation is cheaper than emulation:
GOOS=windows go build ./... catches most portability breaks.
4. Coverage Gate
- run: go test -race -coverprofile=coverage.out ./...
- name: enforce coverage floor
run: |
total=$(go tool cover -func=coverage.out | awk '/^total:/ {sub(/%/,"",$3); print $3}')
echo "coverage: ${total}%"
awk -v t="$total" 'BEGIN { exit (t < 70.0) }'
Gate on a floor that ratchets up, not a target that gets gamed. Exclude
generated code via //go:generated files' build tags or grep filters,
not by lowering the floor.
5. Makefile — Local Mirror of CI
CI must run what developers run. One definition, two callers:
.PHONY: build lint test vuln ci
build:
go build ./...
lint:
golangci-lint run
test:
go test -race -shuffle=on -coverprofile=coverage.out ./...
vuln:
go tool govulncheck ./... # pinned via `go get -tool` (Go 1.24+)
ci: build lint test vuln
If CI does anything make ci doesn't, developers discover failures
only after pushing. Keep them identical.
6. Speed Rules
- Split lint / test / vuln into parallel jobs (as in §1).
go test ./...already parallelizes across packages; don't shard a small repo.- Integration tests behind a build tag run in a separate job or on a
schedule, not on every push:
go test -tags=integration ./.... - If the build cache misses constantly, check that
go.sumis the cache key input (setup-go does this) and that jobs don't mutate it.
7. Security Gates
Beyond govulncheck: CodeQL (security-extended) and gosec as SARIF
uploads to the Security tab, and Trivy on any container image you build.
Start gosec with -no-fail to collect a baseline, then make new findings
fail the PR.
8. Dependency Updates and Action Pinning
- Dependabot for both
gomodandgithub-actions, weekly, with minor and patch updates grouped into one PR. Majors stay separate — they need a changelog read. - Pin third-party actions by commit SHA with the version in a comment; a tag can be moved to malicious code, and Dependabot updates SHA pins.
9. Workflow and Repository Hardening
- Per-job permissions on top of a read-only default.
- No
pull_request_targetworkflow that checks out and runs fork code with secrets available. - Branch protection: required checks, a review, no force pushes.
- An AI reviewer loaded with the go-code-review skill is an extra reviewer, never a check that can approve on its own.
Snippets for sections 7-9: references/security-and-automation.md.
Tag-triggered releases — GoReleaser, signing, SBOMs, provenance — are
owned by the go-build-release skill.
Verification Checklist
- Pipeline has all four gates: build, vet+lint, test -race, govulncheck
- Go version sourced from go.mod (
go-version-file), not duplicated permissions: contents: readset at workflow level- Tests run with
-race -shuffle=on .golangci.ymlcommitted with a curated linter set- Every
nolintcarries a linter name and a reason - Matrix limited to versions/platforms actually supported
- Coverage floor enforced, generated code excluded
make cireproduces the pipeline locally, byte-for-byte- Integration tests isolated behind tags, not slowing every push
- CodeQL/gosec results uploaded as SARIF; images scanned before push
- Dependabot covers modules and actions; third-party actions pinned by SHA
- Per-job permissions; no fork code run with secrets