Lesson 11 of 28
Module 3 · Skill — Codify `github-actions-k8s-deploy`
Why this becomes a skill
You wired a pipeline that builds a Go service, pushes an image to GHCR with a commit-SHA tag, spins up a kind cluster inside the runner, and helm-installs the chart. The moving parts — permissions, image tagging, in-CI cluster, helm install with value overrides — are the exact shape every platform team uses, and writing them by hand every new repo is where bugs get copy-pasted. A skill captures the correct shape so the next repo starts clean.
You also broke the workflow by stripping packages: write. You now know what missing-permission failures look like. The skill will inherit that knowledge.
Codify
Send Claude:
"Codify the session we just had as a skill at
.claude/skills/github-actions-k8s-deploy/SKILL.md. Scope: CI/CD for a Go service that builds, tests, pushes to GHCR, deploys to a kind cluster inside the runner via a Helm chart. Parameterise: repo name, Go version, image tag scheme (default${{ github.sha }}+latest), chart path, namespace, smoketest endpoint. Required inputs: the caller tells the skill the Dockerfile path, the test command, and the chart directory. Keep SKILL.md under 140 lines."
Read SKILL.md. The permissions block, the secrets.GITHUB_TOKEN flow, and the order of steps all matter — confirm you can explain each.
Refine
"Add the correct
permissions:block at the JOB level (contents: read,packages: write,id-token: writefor future OIDC) — not workflow level, not missing. Include a comment explaining thatid-token: writeis only needed when federating to a cloud provider (not needed for GHCR).""Remember the Break it on purpose probe: when
packages: writewas missing, thedocker/build-push-actionstep failed withdenied: installation not allowed to Write organization package. Add aKnown failuresentry pointing to that exact error and the fix.""Add a Step that prints
kubectl cluster-infoimmediately afterhelm/kind-actionsets up the cluster — that way when helm-install fails withKubernetes cluster unreachable, the log proves whether the cluster was actually up."
Validate in a fresh context — happy path AND adversarial
4a — Happy path
New Claude session, pointed at a different empty GitHub repo:
"Read
.claude/skills/github-actions-k8s-deploy/SKILL.mdand apply it to this repo. It's a Python FastAPI service, test commandpytest, Dockerfile at./Dockerfile, chart at./chart, smoketest endpoint/health. Push tomainand tell me the Actions run URL."
The skill needs to adapt to Python/FastAPI from Go (different setup action, different test command). If it hard-codes Go specifics, the skill was under-parameterised — refine.
4b — Adversarial
- Forked contributor scenario: ask the skill to apply to a fork (where
secrets.GITHUB_TOKENhas read-onlypackagesscope by default). Does the skill notice the fork constraint and warn, or does it silently produce a workflow that fails on every fork PR? - Broken test: feed it a repo where
pytestcurrently fails. Does the workflow fail at the test step (good, loud), or does it skip straight to build (bad)? - Protected branch with merge queue: the
mainbranch in the target repo has required status checks + a merge queue. Does the skill'son: push: branches: [main]trigger play correctly with the merge queue, or does it race?
Silent wrongness on any of these — in particular the fork case, which real open-source projects hit — is a bug. Fix in Refine and re-test.
Promote deterministic commands to scripts/
The workflow YAML itself is the deterministic artefact. The non-deterministic parts are the Dockerfile, the test command, and the chart — all of which belong in the target repo, not the skill.
That means the scripts/ promotion here looks different from earlier modules: the skill should ship a workflow template at .claude/skills/github-actions-k8s-deploy/templates/deploy.yml (literal template with {{PLACEHOLDER}} tokens the skill substitutes during application). Prompt:
"Split SKILL.md from the template. Move the literal workflow YAML to
templates/deploy.ymlwith tokens{{GO_VERSION}},{{TEST_CMD}},{{DOCKERFILE_PATH}},{{CHART_PATH}},{{NAMESPACE}},{{HEALTHCHECK_PATH}}. SKILL.md now describes how to substitute and commit the template."
Read the template. Every token should appear at least once in the final workflow, and no token should leak through unsubstituted — add a post-substitution grep check in the skill.
Know the boundary
At the top of .claude/skills/github-actions-k8s-deploy/SKILL.md:
- This skill handles: Go / Python / Node services (parameterised toolchain setup), GHCR image push with short-lived
GITHUB_TOKEN,kind-in-CI deploy via Helm chart, a single smoketest HTTP endpoint, and thepackages: writepermission most people forget. - This skill does NOT handle: cloud deploys (GKE/EKS/AKS — requires OIDC federation and cloud IAM setup, see module 7), multi-arch images (add
docker/setup-qemu-actionyourself), matrix builds, monorepo path filters, artifact caching beyond buildx, or deploys to shared clusters (requires a pre-existing KUBECONFIG secret or cloud identity, which is module 7 territory).
You're done when
A fresh Claude session applies the skill to a different-language repo from one prompt, the resulting workflow runs green, and the adversarial probes in 4b each produce a clear, actionable failure rather than silent wrongness.