GitHub Actions on rknightion / m7kni repos (all projects)
One of the global rules my agents load on every session, published as they load it. Links to reference files point at documents I do not publish.
- Source
- rob/agents/policy/claude/github-actions-oss.md
- Revision
- ca34e4e
Read ~/.claude/reference/github-actions-oss.md before editing or diagnosing GitHub Actions workflows.
Shared reusables live in rknightion/.github/.github/workflows/ — ci-adjacent checks
(actionlint, zizmor, dependency-review, scorecard, codeql, docker-security),
helm-validate, auto-rc, arm-automerge, ghcr-cleanup, container-publish, binaries.
Call one before writing a new job. Callers are thin: on:, permissions:, uses: …@<release SHA> # vX.Y.Z. Renovate maintains the pin. Never @main for a cross-repo call.
Permissions on a reusable call
A called workflow’s job permissions are capped by the calling job’s and validated at STARTUP,
before any if: is evaluated. So a permission needed by a job that will be skipped is still
mandatory — removing one because “that job no longer runs” is a startup_failure with zero jobs
and no log (gh run view --log-failed says only log not found).
When copying a caller between repos, re-derive the permissions for any local uses: ./… call from
that repo’s own working caller. publish.yml differs per repo; one needing actions: read fails
under a block copied from one that doesn’t.
workflow_run
-
workflows:matches the triggering workflow’sname:field, not its filename. A mismatch never errors — the workflow silently stops firing. Confirm the live name before writing it. -
github.shais the default-branch HEAD, not the commit that was tested; it races ahead on rapid pushes. Usegithub.event.workflow_run.head_sha. -
It fires for failed and cancelled runs too — gate on
github.event.workflow_run.conclusion == 'success'. -
Deleting or moving a file inside
rknightion/.githubis a fleet change, however dead it looks at HEAD. Before removing one, grep the reusables at every still-referenced release tag — not justmain— for a path that resolves it, and leave aDO NOT DELETEcomment naming the pinned versions that consume it. -
Diagnosing a workflow that broke with no local diff: check the reusable’s repo for commits in the window, not the consuming repo’s history. Compare the last green run’s timestamp against
gh api "repos/rknightion/.github/commits?path=<workflow>". -
A green fix is proven by re-running the failed run at its unchanged SHA (
gh run rerun <id> --failed). Same commit, same caller, only the other repo changed — that isolates the cause in a way a new push never can.
A caller pinned to a release SHA
does not receive a fix landed in the reusable after that release. When a shared workflow starts
failing, check whether a later release already fixes it before diagnosing the failure or writing
anything: gh release list -R rknightion/.github, then
git log <pinned-tag>..<latest-tag> -- .github/workflows/<file>.
container-publish built type=oci,dest=<path>.tar, an OCI tar. An OCI tar carries index.json
and oci-layout, never the manifest.json a Docker archive has, and Trivy reads a Docker archive
tar or an OCI directory but not an OCI tar. The fix, tar=false, was already released in
v1.18.1; eleven repos were still pinned to v1.18.0 and so never picked it up. The whole repair was a
one-line pin bump in each caller, and no cross-repo write at all.
- Find the caller that is already ahead and read it as the control. One repo on v1.18.1 showed Trivy initialising and scanning normally, which proved the fix without touching anything.
- A caller still red after the bump is not the same bug. That control repo stayed red because Trivy then found real HIGH/CRITICAL packages: the gate working, not the scanner breaking. Do not read “still failing” as “fix did not work” without reading which step failed and why.
- One broken release plus Renovate lag is a fleet-wide outage with a per-repo fix. Sweep
gh api -X GET search/code -f q='container-publish.yml user:<owner>'for every caller and its pinned version before assuming it is one repo’s problem.
Pinned tools
Never hand-transcribe a checksum. Verify against the release’s own CHECKSUMS/.sha256 asset.
kubeconform -ignore-missing-schemas passes while validating nothing. It skips every kind with
no schema, so a chart’s CRDs go unchecked while the job stays green. Give kubeconform the schema
instead — schema-locations: default <datreeio/CRDs-catalog URL> — and leave the flag off.
Pin -kubernetes-version to the floor Chart.yaml’s kubeVersion claims, not the newest.
Concurrency and cost
Public repos get free GitHub-hosted minutes; private ones bill, so a private caller passes
runs-on: arc-arm64 where a reusable supports it (and harden: false — harden-runner is
Enterprise-tier on self-hosted and needs an agent on the host).
Never make a job wait
on another job by polling — it holds a runner slot the thing it waits for needs. Use workflow_run
or needs:.
A release workflow queues; CI cancels. CI takes group: ci-${{ github.ref }} with
cancel-in-progress: true, because only the newest commit’s result matters. Anything that publishes
(TestFlight, a container or binary release, a store upload) takes one shared group for the whole
repo with cancel-in-progress: false, so concurrent runs wait instead of racing. Two release runs
in flight derive the same next build number from the registry or store and then fight over it, and
cancelling one mid-publish can leave the artifact half-uploaded. A release workflow with no
concurrency: block at all is the same defect.
rknightion is a User account, so it cannot have runner groups or org-level Actions policy, and
allows_public_repositories does not exist for it. Self-hosted runners there would be per-repo with
no group controls — do not put public-repo CI on them.