Github actions
Skill milanhorvatovic/skill-system-foundry/.agents/skills/github-actions
Guides the creation, modification, and review of GitHub Actions workflows in the Skill System Foundry repository. Covers workflow YAML syntax, trigger configuration, permission models, SHA-pinned actions, matrix strategies, artifact passing, and concurrency control. Triggers when asked to create or modify a workflow, add a CI job, configure triggers or permissions, pin an action to a SHA, troubleshoot a failing workflow, or review a workflow change. Also triggers on phrases like "add a workflow," "fix the CI," "pin this action," "workflow permissions," or "why is this action failing." Use this skill for any work involving .github/workflows/ YAML files.From its SKILL.md
npx -y skills add milanhorvatovic/skill-system-foundry --skill github-actionsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.7 KB, as published. Nobody here has run it
GitHub Actions Skill
Guides the creation, modification, and review of GitHub Actions workflows in the Skill System Foundry repository. Codifies the conventions established across the repository's existing workflows.
Repository Workflows
| Workflow | File | Trigger | Jobs |
|---|---|---|---|
| Tests + coverage | python-tests.yaml | Push to main, PRs, workflow_call | test (matrix), validate-examples, reference-conformance, bundle-extract-smoke |
| Coverage badge | coverage-badge.yaml | workflow_run after Python tests | badge |
| CI helper tests | ci-helper-tests.yaml | Push/PR (path-filtered to CI helpers) | helper-tests (matrix) |
| Shell lint | shellcheck.yaml | Push/PR (path-filtered to .sh) | shellcheck |
| Action-pin verify | verify-action-pins.yaml | Push to main, PRs | verify |
| Workflow lint | actionlint.yaml | Push to main, PRs | actionlint |
| Codex code review | codex-code-review.yaml | PR events (non-draft) | review (read-only), publish (write) |
| Release-label check | verify-pr-release-label.yaml | PR events | verify-release-label (required) |
| Supply-chain scorecard | scorecard.yaml | Schedule, push to main, branch-protection change | analysis |
| CodeQL | codeql.yaml | Push to main, PRs, schedule | analyze |
| Tool-catalog drift | tool-catalog-drift.yaml | Schedule, workflow_dispatch | detect-and-pr |
| Release prep | release-prep.yaml | workflow_dispatch | prepare, test, open-pr |
| Release tag | release-on-merge.yaml | Release PR merged to main | tag |
| Release bundle | release.yaml | v*.*.* tag push | release |
Hard Requirements
Actions Pinned to Commit SHAs
Every action must be pinned to a full 40-character commit SHA, not a tag. Include a comment with the tag and semver for readability:
# Correct
- uses: actions/checkout@de0fac2e5ef641dbfe0fef2a1de4a5c3a0d70dce # @v6 as 6.0.2
# Wrong — tag-only reference
- uses: actions/checkout@v4
When updating an action version:
- Find the new tag's commit SHA on the action's repository releases page
- Replace the SHA in the workflow
- Update the comment to reflect the new tag and version
This rule is enforced by .github/scripts/verify-action-pins.py, which the verify-action-pins.yaml workflow runs on every PR and push to main. Any uses: line that is not a 40-character lowercase commit SHA (or ./local-path / docker://...) fails CI. Run the script locally with python .github/scripts/verify-action-pins.py to check before pushing.
App Token Identity
When minting a GitHub App token with actions/create-github-app-token, use the client-id input — app-id is deprecated (the action carries a deprecationMessage pointing to client-id). Across these OSS repos each automation identity wires a <PREFIX>_CLIENT_ID repository variable for the App's client ID plus a repository secret holding its PEM private key; the private-key secret name is not uniform (AUTOMATION_PRIVATE_KEY for the automation App, RELEASE_APP_PRIVATE_KEY for the release App), so take the exact names from the list below:
- oss-automation-bot —
vars.AUTOMATION_CLIENT_ID+secrets.AUTOMATION_PRIVATE_KEY(the workhorse: dependency automation, opening/approving PRs). Wired in this repo'stool-catalog-drift.yamland the release-PR approval inrelease-prep.yaml. - oss-release-bot —
vars.RELEASE_CLIENT_ID+secrets.RELEASE_APP_PRIVATE_KEY(the release identity: opens the release PR, tags, and publishes). Wired inrelease-prep.yaml,release-on-merge.yaml, andrelease.yaml.
# Correct
- uses: actions/create-github-app-token@<sha> # @v3 as 3.x
with:
client-id: ${{ vars.AUTOMATION_CLIENT_ID }}
private-key: ${{ secrets.AUTOMATION_PRIVATE_KEY }}
<!-- yaml-ignore -->
# Wrong — deprecated app-id input
- uses: actions/create-github-app-token@<sha> # @v3 as 3.x
with:
app-id: ${{ vars.AUTOMATION_APP_ID }}
private-key: ${{ secrets.AUTOMATION_PRIVATE_KEY }}
Least-Privilege Permissions
Scope permissions as narrowly as possible. Default to contents: read. Only add write permissions where required, and prefer job-level over workflow-level permissions:
# Preferred: job-level permissions
jobs:
test:
permissions:
contents: read
deploy:
permissions:
contents: write
# Acceptable: workflow-level when all jobs need the same
permissions:
contents: read
The Codex code review workflow demonstrates the ideal pattern: a read-only review job followed by a write-capable publish job, isolating the permission boundary.
No Hardcoded Secrets
Secrets are accessed via ${{ secrets.VARIABLE_NAME }}, variables via ${{ vars.VARIABLE_NAME }}. Never echo secrets to logs. Shell scripts called by workflows validate required environment variables at the top with ${VAR:?}.
Conventions
Trigger Configuration
- Push to
main— for CI that should run on every merge (tests, badge updates) pull_request— for validation that gates merges (tests, lint, code review)- Path filtering — use
paths:to avoid triggering on unrelated changes.shellcheck.yamlonly runs when.shfiles change - Activity types — specify explicit types for PR workflows when not all events are relevant:
types: [opened, reopened, synchronize, ready_for_review]
Runner Selection
ubuntu-latestis the default for all jobs- Add
windows-latestvia matrix only when cross-platform testing is needed (currently onlypython-tests.yaml) - Do not add macOS unless explicitly required — it consumes more CI minutes
Matrix Strategy
Use matrix for cross-platform or multi-version testing. Set fail-fast: false so all combinations run even if one fails:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.12"]
Concurrency Control
Use concurrency groups with cancel-in-progress: true to avoid redundant runs on rapid pushes:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
The Codex review workflow uses PR-number-based concurrency: codex-review-${{ github.event.pull_request.number }}.
Artifact Passing Between Jobs
When jobs with different permission scopes need to share data, use upload/download artifact pairs:
- Job A (read-only) uploads an artifact
- Job B (write) downloads and processes it
This pattern is used by both python-tests.yaml (coverage total) and codex-code-review.yaml (review output). Never pass data through workflow outputs for sensitive content.
Timeout Discipline
Set explicit timeout-minutes on jobs that call external services or could hang:
jobs:
review:
timeout-minutes: 30
publish:
timeout-minutes: 5
Jobs that only run shell commands or Python scripts can rely on the GitHub default (6 hours) but consider setting a reasonable limit.
Adding a New Workflow
- Create
.github/workflows/<name>.yaml - Define the trigger (push, pull_request, release, workflow_dispatch)
- Set the minimum required permissions at job level
- Pin all actions to commit SHAs with version comments
- Add path filtering if the workflow only applies to specific file types
- Add concurrency control if the workflow could run redundantly
- Test the workflow on a branch before merging to
main
Modifying an Existing Workflow
- Identify which workflow file to change
- Check if the change affects permissions — if adding a write operation, it may need to move to a separate job with elevated permissions
- If adding a new action, pin it to a commit SHA immediately
- If changing triggers, verify the workflow still runs when expected and does not run unnecessarily
- Test on a branch — push a PR to verify the workflow triggers correctly
Reviewing Workflow Changes
Check these in order:
- Are all actions SHA-pinned? Enforced automatically by
verify-action-pins.yaml; rely on the gate instead of reviewing this by eye. - Are permissions minimal? Does the workflow request more access than it needs? Does a write permission belong at job level instead of workflow level?
- Is the permission boundary maintained? Read-only analysis and write-capable publishing should be in separate jobs
- Are new environment variables validated? Scripts called by the workflow should check
${VAR:?}at the top - Is path filtering appropriate? Does the workflow run on changes it does not need to process?
- Are concurrency groups set? Could rapid pushes cause redundant runs?
Common Mistakes
- Using a tag reference (
@v4) instead of a SHA — breaks reproducibility and is a security risk - Granting
contents: writeat workflow level when only one job needs it - Missing path filter — workflow runs on every PR even when irrelevant files changed
- Forgetting to update the version comment after changing a SHA
- Passing sensitive data through workflow outputs instead of artifacts
- Missing
timeout-minuteson jobs that call external APIs - Adding a new action without verifying its SHA from the official repository