From null-ptr-exception-skills
This skill should be used when writing, modifying, or reviewing GitHub Actions workflow YAML files. Covers action version pinning, Docker build/push/release patterns, Node.js runtime deprecation, and trunk-based CI/CD design. Use when the user asks to "create a workflow", "fix CI", "update GitHub Actions", "add a release step", "pin action versions", or is editing .github/workflows/*.{yml,yaml} files.
How this skill is triggered — by the user, by Claude, or both
Slash command
/null-ptr-exception-skills:github-actions-cicdThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Opinionated rules for writing reliable, maintainable GitHub Actions workflows.
Opinionated rules for writing reliable, maintainable GitHub Actions workflows.
Pin actions to full commit SHA. Add exact release tag in comment for auditability.
# GOOD — SHA for immutability, exact tag for humans
- uses: actions/checkout@9f698171ed81b15d1823a05fc7211befd50c8ae0 # v6.0.3
# BAD — moving tag, can be retargeted
- uses: actions/checkout@v6
# BAD — ambiguous comment, v6 could be v6.0.0 or v6.0.3
- uses: actions/checkout@9f698171ed81b15d1823a05fc7211befd50c8ae0 # v6
To find the SHA for a release tag (works for both annotated and lightweight tags — GitHub Actions resolves both correctly):
gh api repos/{owner}/{repo}/git/ref/tags/{tag} --jq '.object.sha'
To find the latest release tag:
gh api repos/{owner}/{repo}/releases/latest --jq '.tag_name'
GitHub deprecates Node.js versions used by actions on a regular cycle. When CI logs show warnings like Node.js 20 actions are deprecated, update all actions to their latest major versions.
Check all actions in a workflow at once:
grep -E '^\s+uses:' .github/workflows/*.{yml,yaml}
Then look up the latest version for each and update both the SHA and the comment tag.
Separate building from publishing. Not every merge to main should trigger a release.
Pattern: always build, conditionally push and release.
- name: Check if version exists
id: check
run: |
if version_exists_in_registry; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Build image
uses: docker/build-push-action@...
with:
push: false
load: true
- name: Push image
if: steps.check.outputs.exists == 'false'
run: docker push ...
- name: Create release
if: steps.check.outputs.exists == 'false'
run: ...
Why: Building always validates the Dockerfile. Pushing and releasing only happen when the version in package.json (or equivalent) is bumped — an intentional release action.
When a step is conditional (not every run should execute it), skip it gracefully instead of failing the whole workflow.
# GOOD — skip with a message
if version_already_exists; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Version already exists — skipping push"
fi
# BAD — fails the entire workflow
if version_already_exists; then
echo "::error::Version already exists"
exit 1
fi
Reserve exit 1 for conditions that genuinely indicate a problem (security checks, lint failures, broken tests). Use step-level if: conditions for optional work.
Use the generate-notes API to get auto-generated changelog, then append custom content.
- name: Create release
run: |
VERSION=v${{ steps.version.outputs.version }}
git tag "$VERSION"
git push origin "$VERSION"
NOTES=$(gh api repos/${{ github.repository }}/releases/generate-notes \
-f tag_name="$VERSION" --jq .body)
printf '%s\n\n## Docker Image\n\n```\ndocker pull %s:%s\n```\n' \
"$NOTES" "$IMAGE" "${{ steps.version.outputs.version }}" > /tmp/release-notes.md
gh release create "$VERSION" -F /tmp/release-notes.md
Why not --generate-notes flag directly? You can combine --generate-notes with --notes to prepend custom text, but the API approach gives full control over composition — e.g. appending a Docker image section after the changelog rather than before it.
For projects where main is the trunk:
docker:
runs-on: ubuntu-latest
needs: e2e
if: github.ref == 'refs/heads/main'
permissions:
contents: write # for git tag + release
packages: write # for ghcr push
Permissions: Use contents: write (not read) when the job creates git tags or GitHub releases.
| Mistake | Fix |
|---|---|
push: ${{ github.ref == 'refs/heads/main' }} combines build+push | Separate into build (always) and push (conditional) steps |
contents: read on a job that creates tags/releases | Use contents: write |
| Version check that fails the workflow | Use skip pattern with step outputs |
| Need to append (not prepend) custom content to release notes | Use the API to get notes, then compose |
Action pinned to @v6 without SHA | Pin to SHA, add exact tag in comment |
Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub null-ptr-exception/skills --plugin null-ptr-exception-skills