GitHub Workflows
GitHub Workflows Documentation
This document provides an overview of all GitHub Actions workflows used in the launcher project.
Last Updated: 2026-08-24
Workflow Summary
| Workflow | File | Triggers | Purpose |
|---|---|---|---|
| CI | ci.yml | push, PR, merge_group, schedule, manual | Testing, linting, building, cross-platform binaries |
| Deploy Docs | deploy-docs.yml | push to main (docs paths), workflow_dispatch | Multi-version docs deployment |
| Release | release.yml | version tags | Release with GoReleaser, SBOM, docs deploy |
| Create Release | release-create.yml | workflow_dispatch | Pre-release test gate + tag creation |
| PR Review | pr-review.yml | pull_request, merge_group | Two-pass AI code review via claude-max-proxy |
| Claude | claude.yml | PR/issue/comment events | @claude AI assistant |
The last five workflows are thin callers that delegate to reusable workflows in go-kure/.github. See go-kure/.github AGENTS.md for their full documentation.
CI Workflow
File: .github/workflows/ci.yml
Name: CI
Triggers
- Push to:
main,develop,release/* - Pull requests to:
main,develop - Merge group (merge queue’s temporary branch — required checks must report here)
- Schedule: 4am UTC daily (catch external changes)
- Manual dispatch
Concurrency
Uses github.ref to cancel superseded runs on the same branch or PR:
Job Dependency Graph
On merge_group events (merge queue), lint/test/build run against the queue’s
temporary branch — the merged result — before the PR is allowed to land.
Jobs Detail
| Job | Check Name | Timeout | Dependencies | Purpose |
|---|---|---|---|---|
changes | detect-changes | 2 min | — | Path filter: go: and docs: outputs control downstream jobs |
validate | lint | 20 min | changes | go-version, fmt, tidy, vet, lint, tool-version parity (golangci-lint pin across Makefile/ci.yml/docs), govulncheck doc parity; diff-based lint on PRs |
test | test | 25 min | changes | Unit tests with race detection and coverage (-race); CGO enabled |
security | Security | 15 min | changes | govulncheck (symbol scan, allowlist-gated), outdated deps check, sensitive file scan |
action-pins | action-pins | 2 min | — | Fails if any third-party uses: ref is not pinned to a 40-char commit SHA (go-kure/.github composite action) |
coverage-check | Coverage Check | 5 min | test | 80% threshold, Codecov upload, PR sticky comment |
build-binaries | Build kurel | 10 min | changes, test | Build kurel linux/amd64 binary; uploaded as artifact |
docs-build | docs-build | 15 min | changes | Hugo site build for docs; go + Hugo caches; runs the shared No-Downstream-References guard (check-forbidden-terms action, --full-tree) + a vendored-copy drift check + the canonical check-doc-sync/check-links actions (structure + rendered-link check) |
pin-impact | pin-impact | 3 min | — | Renders and gates on the real impact of a go-kure/.github pin bump: resolves each referenced action’s scripts/*.sh (and their sourced siblings), intersects against the compare diff, fails if a consumed path changed (PR only, go-kure/launcher#358) |
build | build | 1 min | validate, test, build-binaries, docs-build, coverage-check, action-pins, security, pin-impact | Aggregation gate |
cross-platform | Cross-Platform Build | 15 min | build-binaries | Matrix: linux × amd64/arm64 (main + release/* only) |
validate-manifests | validate-manifests | 10 min | changes | kurel build + flux schema validate against the default (embedded) and ecosystem (schemas.fluxoperator.dev) catalogs for a representative examples/*.yaml subset; continue-on-error: true, not in build’s gate (go-kure/launcher#292) |
analyze-changes | Analyze Changes | 5 min | — | Changed files summary, breaking change warning for pkg/ (PR only) |
Cross-Platform Matrix
Runs on main and release/* branches only (not PRs):
| OS | amd64 | arm64 |
|---|---|---|
| linux | ✅ | ✅ |
Configuration
- Go Version: read from
mise.toml(single source of truth) - yq / lychee / Flux CLI versions: read from
mise.tomlat run time, same pattern as Go — no hand-copied literal exists in any workflow to fall out of sync - Golangci-lint Version:
v2.13.2 - govulncheck Version:
v1.7.0(pinned via theGOVULNCHECK_VERSIONworkflow env) - Coverage Threshold:
80% - Test Timeout:
5m(longer than kure; builds include CGO)
Features
- No-Downstream-References guard —
docs-buildruns the sharedgo-kure/.githubcheck-forbidden-termsaction, which scans--full-treeon every event so a PR and the merge queue produce identical results (scan parity). A drift-check step keeps the vendored copy thatscripts/release.shuses (site/scripts/check-forbidden-terms.sh) byte-identical to canonical - Doc-sync checks —
docs-build(Layers 1/2) anddoc-gate(Layer 3) run the canonicalcheck-doc-sync,check-linksandcheck-doc-gateactions fromgo-kure/.github; launcher no longer vendors its own copies undersite/scripts/ - Manifest schema validation —
validate-manifestsbuilds a representative subset ofexamples/*.yamlviakurel buildand validates the output against fluxcd/flux-schema’sdefault(embedded) catalog plus itsecosystemcatalog (fetched from schemas.fluxoperator.dev — network access required) (make validate-manifests, same command locally).continue-on-error: truefor its first cycle and excluded frombuild’s aggregation gate andDEVELOPMENT.md’s required-checks list — promoting it to required is a deliberate follow-up (go-kure/launcher#292) - Pin-impact gate —
pin-impact(PR only) renders the real impact of ago-kure/.githubpin bump before merge: which actions it touches, whichscripts/*.sheach resolves to (one level ofsourceincluded), intersected against the bump’s actual diff. Fails closed on anything it can’t confidently resolve — an unrecognizedaction.ymlshape (a nesteduses:step, more than onerun:step, arun:step with noscripts/*.shreference), an unrecognized or dot-segmentsourceexpression, or a non-ahead compare — rather than under-reporting. A maintainer who has reviewed a real hit and judged it safe adds thepin-impact-acklabel to merge anyway — same convention ascheck-doc-gate’sdocs-skiplabel; there is no other override. Ported fromgo-kure/kure(go-kure/kure#729) after go-kure/launcher#358 turned up the identical blind spot here. Rerun gotcha: thestrip-ackstep only runs when the triggering event’s action wassynchronizeorreopened; re-running a stale/failed run of one of those (gh run rerun, or the Actions UI) replays that same original action and silently strips a freshly-addedpin-impact-ackagain before the gate re-checks it, even though nothing was pushed. A rerun of anopened/labeled/unlabeled-triggered run is unaffected —strip-ackskips it either way. Scoped to same-repo PRs:strip-ack’sif:also requires the PR’s head repo to equal this repo, so it never runs at all on a fork PR — but that’s moot, since the gate separately forcesPIN_IMPACT_ACK=falseunconditionally on forks; a fork PR has no acknowledgment path regardless of labels. Add (or re-add) the label rather than rerunning, on a same-repo PR; full writeup ingo-kure/.github’sdocs/standards.md§ “Pin-impact-ack” - Path filtering —
dorny/paths-filterskips jobs when unrelated files change - Diff-based lint — on PRs, lint only checks new/changed lines (
--new-from-rev) - CGO enabled — test job installs
build-essentialfor cgo-dependent packages - Binary artifact —
kurellinux/amd64 binary uploaded per run (7-day retention) - Cross-platform artifacts — 5 binaries uploaded per main push (30-day retention)
- Runs on draft PRs — no draft gate on any job (2026-08-19, GitLab
mr-reviewparity — draft blocks merge only, via branch protection, not what CI runs) - make install guard — every job that calls
makeinstalls it first (runner image lacks it) - govulncheck allowlist — the
Securityjob runsgovulncheck -scan symbol -format json, then gates the report through the sharedgo-kure/.githubgovulncheck-gatecomposite action (same fail-closed script kure uses), which blocks on any OSV ID with a reachable symbol trace that isn’t in the action’sallowlistinput. The action fails closed: a missing, empty, or unparseable report is a gate error (exit 2), never a silent clean result, and reachable-vs-allowed advisories are printed to the job log so accepted risk stays visible rather than looking clean.- Currently allowlisted:
GO-2026-5377(external-secrets controller privilege escalation). Launcher only importsexternal-secrets/apisto generate CRD manifests; reachable traces are generated deepcopy boilerplate and package init, never a reconciler. The apis module is untagged and the Go vuln DB records no fixed version (Fixed in: N/A), so no dependency bump can clear it.
- Currently allowlisted:
Deploy Docs Workflow
File: .github/workflows/deploy-docs.yml
Name: Deploy Docs
Triggers
- Push to main (paths:
site/**,docs/**,*.md,CHANGELOG.md,DEVELOPMENT.md,scripts/gen-versions-toml.sh) - Manual dispatch with inputs:
version_slot,version_label,set_latest
How It Works
- Determines version parameters (dev for push to main, explicit slot for manual dispatch)
- Reads Hugo, Go and yq versions from
mise.toml - Runs
scripts/gen-versions-toml.shto generate versioned Hugo config overlay - Builds the Hugo site targeting
https://www.gokure.dev/launcher/<slot>/ - If
set_latest=true, also builds athttps://www.gokure.dev/launcher/ - Checks out
go-kure/go-kure.github.ioand deploys to thelauncher/subdirectory
Trigger Matrix
| Event | Deploys To | BaseURL |
|---|---|---|
Push to main (docs paths) | launcher/dev/ | www.gokure.dev/launcher/dev/ |
workflow_dispatch | launcher/<slot>/ | www.gokure.dev/launcher/<slot>/ |
workflow_dispatch + set_latest=true | launcher/<slot>/ + launcher/ | both |
Concurrency
Per-slot group (deploy-docs-<slot>) with cancel-in-progress: false — deploys queue rather
than cancel, so a race between two slot deployments doesn’t corrupt the site.
Preservation
Only the target slot is replaced. Other launcher/v*/, launcher/dev/, CNAME, and .nojekyll
are preserved. The root launcher/ files are only overwritten when set_latest=true.
Authentication
Requires DEPLOY_TOKEN secret — a PAT with write access to go-kure/go-kure.github.io.
Merge Queue
launcher merges through GitHub’s native merge queue (configured in the main-protection
ruleset, not a workflow file). This replaced the former rebase-check job and auto-rebase.yml
workflow — it is the native equivalent of GitLab’s merged-results pipelines.
How It Works
- A reviewed PR is added to the queue (“Merge when ready”).
- The queue creates a temporary branch combining
main+ the PR and fires amerge_groupevent;lint/test/buildrun against that merged result. - If green, the PR lands on
mainwith the rebase merge method (linear history preserved). If the merged result fails, the PR is dropped from the queue andmainstays green.
Why
- Tests the actual merged result, which
rebase-check(ancestry-only) could not. - No force-pushing contributor branches and no per-merge auto-rebase storm — the queue rebases once, at merge time.
Configuration (ruleset merge_queue rule)
- Merge method:
REBASE(linear history) - Grouping:
ALLGREEN(a failing entry is dropped from the group) - Batch size: 1 (conservative; tune after observing runner load)
- Required checks on the queue:
lint,test,build(must also trigger onmerge_group)
Auto-merge is not enabled — every PR is reviewed and queued manually. The merge queue rule is
managed centrally in go-kure/.github (governance/repository-settings-policy.yaml).
Release Workflow
File: .github/workflows/release.yml
Reusable source: go-kure/.github/.github/workflows/release.yml@main
Triggers
- Push of version tags:
v*(triggered byrelease-create.yml)
Job Sequence
Key Input
Requirements
Secrets: RELEASE_APP_ID, RELEASE_APP_PRIVATE_KEY (kure-release-bot GitHub App)
Create Release Workflow
File: .github/workflows/release-create.yml
Reusable source: go-kure/.github/.github/workflows/release-create.yml@main
Triggers
- Manual dispatch with inputs:
type(alpha/beta/rc/stable/bump),scope(minor/major/prerelease),dry_run(default: false)
Purpose
Pre-release test gate + tag creation. Runs full tests before pushing any tag, so a failing test suite never results in a published release.
Requirements
Secrets: RELEASE_APP_ID, RELEASE_APP_PRIVATE_KEY (GitHub App token, so tag push triggers
subsequent workflows — GITHUB_TOKEN pushes do not trigger workflows).
PR Review Workflow
File: .github/workflows/pr-review.yml
Reusable source: go-kure/.github/.github/workflows/pr-review.yml@main
Triggers
- Pull requests:
opened,synchronize,reopened, on GitHub’s default types merge_group(no filters): required so this check reports on the merge queue’s temporary ref once it becomes a required status check — the queue payload has nopull_requestfield, so the existing fork skip below evaluates false and the job reportsskipped/success as a no-op- Runs on draft PRs the same as ready ones (2026-08-19, GitLab
mr-reviewparity); skips fork PRs ready_for_reviewis not declared, same reasoning asci.yml: it was kept as a rollout-window safety net while the callee (pr-review.yml@main, ingo-kure/.github) still gated ondraft == false(its own parity fix landed 2026-08-19,46dfc88) and dropped once that window closed.
How It Works
Two-pass AI review via the cluster-local claude-max-proxy sidecar:
- Pass 1 — Review: Sends PR diff +
AGENTS.md+.claude/CLAUDE.mdto the review model. Posts up to 3 findings in a structured table as a PR comment. - Pass 2 — Assessment: If the review found issues, an assessment model fact-checks each finding against the actual diff and the provided standards. Posts a verification comment.
Non-blocking: uses continue-on-error: true so review failures never prevent merging.
Context Input
Claude Workflow
File: .github/workflows/claude.yml
Reusable source: go-kure/.github/.github/workflows/claude.yml@main
Triggers
- PR events (opened, synchronize, ready_for_review, reopened)
- Issue comments and PR review comments (when
@claudeis mentioned) - Issues opened or assigned
- PR reviews submitted
Purpose
Runs the anthropics/claude-code-action@v1 agent on any PR or issue that mentions @claude.
The agent has full repo access via checkout and can read code, answer questions, or suggest
changes.
Requirements
Secret: CLAUDE_CODE_OAUTH_TOKEN
Configuration Standards
Go Version
All jobs read go-version from mise.toml dynamically:
mise.toml is the single source of truth. CI jobs need no sync — they read it at run time. Four
other places carry their own copy and do need syncing: every module’s go.mod (root, site/,
site/scripts/kuredepsync/), versions.yaml’s go.current, the README.md shields.io badge, and
DEVELOPMENT.md’s “Go X.Y.Z (managed by mise)” prerequisite line. scripts/sync-go-version.sh
propagates a mise.toml change into all of them in one pass — make sync-go-version runs it
locally, and it also runs as a Renovate postUpgradeTasks command so a bot-proposed mise bump
lands with every copy already in sync. make check-go-version verifies every module’s go.mod against mise.toml;
./scripts/sync-versions.sh check separately verifies the root go.mod against versions.yaml’s
go.current and the README badge against go.mod.
yq, lychee and Flux CLI Versions
Same pattern as Go: a Read <tool> version from mise.toml step greps the value and hard-fails if
empty, every downstream step (cache key, install URL, or the flux2 action’s version: input)
references steps.<tool>-version.outputs.version, and no hand-copied literal exists anywhere else
in the workflow files to fall out of sync. For example:
This replaced three hardcoded yq-4.44.6 cache-key/install pairs (in ci.yml’s validate,
docs-build and doc-gate jobs) plus a fourth in deploy-docs.yml, a hardcoded lychee-0.24.2
pair in docs-build, and a hardcoded version: 2.9.4 on the validate-manifests job’s Flux CLI
install step. The flux-schema plugin (flux plugin install schema@<version>) is a separate pin,
tracked only in site/scripts/validate-manifests.sh’s SCHEMA_PLUGIN_VERSION — a Renovate
customManager in renovate.json proposes its bumps; nothing in mise.toml covers it, since it is
a Flux plugin, not the Flux CLI itself.
Caching
setup-go runs with cache: false; caching is done with explicit actions/cache steps.
The runners are ephemeral ARC pods, so nothing on disk survives between jobs — everything
useful must round-trip through the cache server.
Module cache (dependency-only, one combined step per Go job):
Go build cache (~/.cache/go-build) uses split actions/cache/restore + actions/cache/save
so the log can show exact vs fallback restore (cache-matched-key). The key is source-aware
and split by job purpose so validate (non-race) and test (race+coverage) never overwrite
each other’s entry:
Purpose prefixes: gocache-validate-, gocache-test-race-cover-, gocache-security-,
gocache-build- (the cross-platform job adds <os>-<arch> because cross-compiled artifacts
differ per target). The source hash covers **/*.go, go.mod, go.sum, Makefile, and
**/testdata/**. The save runs only on a non-exact (fallback/miss) restore and only when the
run succeeded, so a broken build never publishes a cache.
Cross-ref scoping caveat. GitHub caches are ref-scoped: a pull_request cache lives on
refs/pull/N/merge and is not visible to the merge_group (merge-queue) run — verified
empirically. The only scope both PR and queue runs can read is the default branch (main).
So these caches are warmed by push-to-main runs; a code-changing PR and its queue run restore
main’s cache via restore-key fallback (not an exact hit) and Go reuses unchanged package
entries internally. This lowers the absolute cost of both runs but does not deduplicate the
PR↔queue build — that duplication is inherent to the merge queue and cannot be removed with
GitHub-scoped caches.
Cache and artifact traffic routes through an in-cluster cache server. Setting
ACTIONS_RESULTS_URL in the workflow env: block ensures upload/download-artifact and
actions/cache see the correct in-cluster URL (the runner binary patch renames the env var
injected into step processes as a side effect).
Self-Hosted Runner
All jobs run on the autops-kube-kure GitHub ARC scale-set. The runner image lacks make,
so every job that calls make installs it first:
Maintenance Notes
- When adding/modifying workflows: Update this document
- Version updates: Run
make sync-go-versionto update Go version across all files - Version check: Run
make check-go-versionto verify consistency - New jobs using
make: Include the install guard step above - Reusable workflows: Changes in
go-kure/.githubtake effect immediately for all callers
See Also
- Makefile — Local development commands
- mise.toml — Local tool versions
- go-kure/.github AGENTS.md — Reusable workflow reference
- scripts/gen-versions-toml.sh — Versioned docs config generator