Skill

actions-conventions

Install
1
Install the plugin
$
npx claudepluginhub jsamuelsen11/claude-config --plugin ccfg-github-actions

Want just this skill?

Add to a custom plugin, then install with one command.

Description

This skill should be used when working with GitHub Actions, writing CI/CD workflows, configuring workflow triggers, or reviewing GitHub Actions configuration.

Tool Access

This skill uses the workspace's default tool permissions.

Skill Content

GitHub Actions Conventions

This skill provides comprehensive guidelines for writing maintainable, secure, and efficient GitHub Actions workflows. Following these conventions ensures consistency across repositories, reduces debugging time, and improves collaboration between team members.

Existing Repository Compatibility

When working with existing repositories, always respect established workflow patterns and conventions:

Discovery Process:

  1. Review all workflows in .github/workflows/ to understand existing patterns
  2. Check for custom composite actions in .github/actions/
  3. Identify the action version pinning strategy currently in use
  4. Note any repository-specific naming conventions or organizational standards
  5. Review workflow run history to understand trigger patterns and success rates

Adaptation Rules:

  • Match existing file naming conventions (some teams use underscores, most use kebab-case)
  • Preserve established trigger patterns unless explicitly asked to change them
  • Maintain consistency with existing job and step naming styles
  • Keep the same level of verbosity in step names as existing workflows
  • Respect existing action version pinning strategies (tags vs SHA)
  • Flag but don't automatically change tag-based pinning without coordination
  • Preserve existing runner selection patterns unless there's a clear reason to change
  • Match existing permissions scoping patterns

When to Propose Changes:

  • Security issues (missing permissions scoping, credentials in logs)
  • Performance problems (missing caches, inefficient matrix builds)
  • Reliability concerns (missing concurrency controls, race conditions)
  • Maintenance burden (duplicated code that could be extracted)

Always explain why changes are necessary and coordinate with the team before making breaking changes to critical workflows.

Workflow File Naming

Use descriptive, kebab-case names that clearly indicate the workflow's purpose:

CORRECT:

# .github/workflows/ci.yml
# .github/workflows/deploy-staging.yml
# .github/workflows/release-npm-package.yml
# .github/workflows/security-scan.yml
# .github/workflows/update-dependencies.yml

WRONG:

# .github/workflows/CI.yml (uppercase)
# .github/workflows/deploy_staging.yml (underscore)
# .github/workflows/workflow1.yml (non-descriptive)
# .github/workflows/do-stuff.yml (vague)

Naming Conventions:

  • Primary CI workflow: ci.yml or test.yml
  • Deployment workflows: deploy-{environment}.yml or cd.yml
  • Release workflows: release.yml or release-{type}.yml
  • Scheduled tasks: {task-name}-scheduled.yml
  • Manual operations: {operation-name}-manual.yml

Workflow Structure

Every workflow should follow a consistent structure:

CORRECT:

name: CI Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

permissions:
  contents: read
  pull-requests: write

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  lint:
    name: Lint Code
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

  test:
    name: Run Tests
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

WRONG:

name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npm test

Step Naming

Always provide a descriptive name: for every step:

CORRECT:

steps:
  - name: Checkout code
    uses: actions/checkout@v4

  - name: Set up Python 3.11
    uses: actions/setup-python@v5
    with:
      python-version: '3.11'

  - name: Install dependencies
    run: pip install -r requirements.txt

  - name: Run unit tests
    run: pytest tests/unit

  - name: Upload coverage report
    uses: actions/upload-artifact@v4
    with:
      name: coverage-report
      path: coverage/

WRONG:

steps:
  - uses: actions/checkout@v4
  - uses: actions/setup-python@v5
    with:
      python-version: '3.11'
  - run: pip install -r requirements.txt
  - run: pytest tests/unit

Naming Guidelines:

  • Use imperative mood (Checkout, Run, Upload, not Checking out, Running, Uploading)
  • Be specific about what's being done (Run unit tests, not Run tests)
  • Include relevant details (Set up Python 3.11, not Set up Python)
  • Keep names concise but descriptive (under 60 characters when possible)
  • Use consistent terminology across workflows

Trigger Configuration

Choose triggers carefully based on your workflow's purpose:

CORRECT - CI Workflow:

on:
  push:
    branches: [main, develop]
    paths-ignore:
      - '**.md'
      - 'docs/**'
  pull_request:
    branches: [main]
  workflow_dispatch:

CORRECT - Deployment Workflow:

on:
  push:
    branches: [main]
    tags:
      - 'v*.*.*'
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy to'
        required: true
        type: choice
        options:
          - staging
          - production
      version:
        description: 'Version to deploy'
        required: true
        type: string

CORRECT - Scheduled Workflow:

on:
  schedule:
    # Run every Monday at 9 AM UTC
    - cron: '0 9 * * 1'
  workflow_dispatch:

WRONG:

on: [push]  # Too broad, runs on all branches

on:
  push:
    branches: ['*']  # Same problem

on:
  schedule:
    - cron: '0 9 * * 1'
  # Missing workflow_dispatch for manual testing

Trigger Selection Guide:

Use CaseRecommended TriggerNotes
PR validationpull_requestUse types: to filter specific events
Main branch CIpush: branches: [main]Validates merged code
Release automationpush: tags: or release:Tag pattern matching
Scheduled maintenanceschedule:Always add workflow_dispatch too
Manual operationsworkflow_dispatch:Use inputs for parameters
External eventsrepository_dispatch:For webhooks from other systems
Dependency updatespull_request: paths:Target specific files

YAML Style Guidelines

Consistent YAML formatting improves readability and reduces errors:

Multi-line Scripts:

CORRECT:

- name: Run integration tests
  shell: bash
  run: |
    set -euo pipefail

    echo "Starting integration tests..."
    export DATABASE_URL="postgresql://localhost/test"
    export API_KEY="${{ secrets.TEST_API_KEY }}"

    npm run test:integration

    if [ $? -eq 0 ]; then
      echo "Integration tests passed"
    else
      echo "Integration tests failed"
      exit 1
    fi

WRONG:

- name: Run integration tests
  run: >
    set -euo pipefail && echo "Starting integration tests..." && export
    DATABASE_URL="postgresql://localhost/test" && export API_KEY="${{ secrets.TEST_API_KEY }}" &&
    npm run test:integration

Shell Selection:

CORRECT:

- name: Run build script
  shell: bash
  run: |
    ./scripts/build.sh

- name: Windows-specific step
  if: runner.os == 'Windows'
  shell: pwsh
  run: |
    Get-ChildItem -Recurse

WRONG:

- name: Run build script
  run: |
    ./scripts/build.sh
  # Missing explicit shell

- name: Cross-platform command
  shell: sh
  run: |
    # sh is too limited, use bash instead
    [[ -f file.txt ]] && cat file.txt

Shell Guidelines:

  • Always specify shell: bash explicitly
  • Use shell: pwsh for PowerShell on all platforms
  • Avoid sh (too limited) and bash -e (redundant with set -e)
  • Add set -euo pipefail at the start of complex scripts
  • Use shell: python for Python one-liners

Expression Quoting:

CORRECT:

- name: Conditional deployment
  if: github.event_name == 'push' && github.ref == 'refs/heads/main'
  shell: bash
  run: |
    VERSION="${{ github.sha }}"
    ENV_NAME="${{ inputs.environment }}"

    echo "Deploying ${VERSION} to ${ENV_NAME}"
    ./deploy.sh "${VERSION}" "${ENV_NAME}"

- name: Use matrix value
  shell: bash
  run: |
    NODE_VERSION="${{ matrix.node-version }}"
    echo "Testing with Node.js ${NODE_VERSION}"

WRONG:

- name: Conditional deployment
  shell: bash
  run: |
    VERSION=${{ github.sha }}  # Unquoted expression
    ENV_NAME=${{ inputs.environment }}

    echo "Deploying $VERSION to $ENV_NAME"

Environment Variables:

CORRECT:

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      NODE_ENV: production
      BUILD_VERSION: ${{ github.sha }}

    steps:
      - name: Build application
        env:
          API_ENDPOINT: ${{ secrets.API_ENDPOINT }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: |
          npm run build

      - name: Another step without extra env vars
        run: |
          echo "Build version: ${BUILD_VERSION}"

WRONG:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Build application
        env:
          NODE_ENV: production
          BUILD_VERSION: ${{ github.sha }}
          API_ENDPOINT: ${{ secrets.API_ENDPOINT }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: |
          npm run build

      - name: Another step
        env:
          # Duplicating env vars in every step
          NODE_ENV: production
          BUILD_VERSION: ${{ github.sha }}
        run: |
          echo "Build version: ${BUILD_VERSION}"

Environment Variable Scoping Rules:

  • Job-level env: for variables used across multiple steps
  • Step-level env: for variables used only in that step
  • Workflow-level env: sparingly (rarely needed)
  • Always scope secrets to the minimal necessary level

Permissions

Always use explicit permissions to follow the principle of least privilege:

CORRECT - Read-Only Workflow:

name: CI

on:
  pull_request:

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run tests
        run: npm test

CORRECT - Workflow with PR Comments:

name: Test Coverage

on:
  pull_request:

permissions:
  contents: read
  pull-requests: write

jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run tests with coverage
        run: npm run test:coverage

      - name: Comment coverage on PR
        uses: actions/github-script@v7
        with:
          script: |
            // Post coverage results

CORRECT - Deployment with OIDC:

name: Deploy to AWS

on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
          aws-region: us-east-1

CORRECT - Job-Specific Permissions:

name: Release

on:
  push:
    tags: ['v*']

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Build artifacts
        run: npm run build

  release:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1

WRONG:

name: CI

on:
  pull_request:

# Missing permissions - uses default which may be too permissive

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

Permissions Reference:

PermissionReadWriteCommon Use Cases
contentsClone repoPush commits, create releasesMost workflows need read
pull-requestsRead PRsComment on PRs, update labelsPR feedback workflows
issuesRead issuesComment, update labelsIssue management
id-tokenN/ARequest OIDC tokenCloud authentication
packagesDownloadPublish packagesContainer registries
deploymentsReadCreate deploymentsDeployment tracking
checksReadCreate check runsCustom CI reporting
statusesReadCreate commit statusesLegacy CI reporting

Default Permissions Strategy:

  1. Start with permissions: contents: read
  2. Add specific permissions as needed
  3. Scope additional permissions to specific jobs when possible
  4. Never use permissions: write-all in production workflows

Concurrency Control

Use concurrency groups to prevent workflow conflicts and save resources:

CORRECT - Pull Request Workflow:

name: PR Validation

on:
  pull_request:

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run tests
        run: npm test

CORRECT - Branch-Based Workflow:

name: CI

on:
  push:
    branches: [main, develop]
  pull_request:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Build project
        run: npm run build

CORRECT - Deployment Workflow (No Cancellation):

name: Deploy to Production

on:
  push:
    branches: [main]

concurrency:
  group: production-deployment
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Deploy application
        run: ./deploy.sh production

CORRECT - Job-Level Concurrency:

name: Multi-Environment Deploy

on:
  workflow_dispatch:
    inputs:
      environment:
        type: choice
        options: [staging, production]

jobs:
  deploy:
    runs-on: ubuntu-latest
    concurrency:
      group: deploy-${{ inputs.environment }}
      cancel-in-progress: false
    steps:
      - name: Deploy to ${{ inputs.environment }}
        run: ./deploy.sh ${{ inputs.environment }}

WRONG:

name: Deploy

on:
  push:
    branches: [main]

concurrency:
  group: deployment
  cancel-in-progress: true # DANGEROUS: Could cancel in-flight deployments

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh

Concurrency Guidelines:

Workflow TypeGroup PatternCancel in Progress
PR validation${{ github.workflow }}-${{ github.event.pull_request.number }}true
Branch CI${{ github.workflow }}-${{ github.ref }}true for PRs, false for main
Deploymentdeploy-${{ environment }}false
Releaserelease-${{ github.ref }}false
Scheduled${{ github.workflow }}false

Runner Selection

Choose appropriate runners based on your needs:

CORRECT - Standard Linux Workflow:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run tests
        run: npm test

CORRECT - Pinned Version for Reproducibility:

jobs:
  build:
    runs-on: ubuntu-22.04
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Build with specific tools
        run: |
          # This workflow requires specific Ubuntu 22.04 dependencies
          ./build.sh

CORRECT - Multi-OS Matrix:

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: [18, 20]

    runs-on: ${{ matrix.os }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - name: Run tests
        run: npm test

CORRECT - Self-Hosted Runners:

jobs:
  deploy:
    runs-on: [self-hosted, linux, deployment]
    steps:
      - name: Deploy to internal network
        run: ./deploy.sh

WRONG:

jobs:
  test:
    runs-on: ubuntu-20.04 # Don't use old versions without reason
    steps:
      - run: npm test

  deploy:
    runs-on: self-hosted # Too broad, should include labels
    steps:
      - run: ./deploy.sh

Runner Selection Guide:

Use CaseRecommended RunnerNotes
General CI/CDubuntu-latestFastest and cheapest
macOS buildsmacos-latestFor iOS, macOS apps
Windows buildswindows-latestFor .NET, Windows apps
Reproducible buildsubuntu-22.04Pin when you need consistency
GPU workloadsubuntu-latest-8-cores or self-hostedLarger runners available
Network-isolatedSelf-hostedFor internal deployments
Cost optimizationubuntu-latest + matrixLinux runners are cheapest

Runner Sizing:

GitHub offers larger runners for performance-critical workloads:

jobs:
  intensive-build:
    runs-on: ubuntu-latest-8-cores
    steps:
      - name: Build large project
        run: cargo build --release

Available sizes (for paid plans):

  • ubuntu-latest-4-cores (16 GB RAM)
  • ubuntu-latest-8-cores (32 GB RAM)
  • ubuntu-latest-16-cores (64 GB RAM)

Action Version Pinning

Pin action versions for security and stability:

CORRECT - SHA Pinning (Most Secure):

steps:
  - name: Checkout code
    uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

  - name: Set up Node.js
    uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
    with:
      node-version: '20'

CORRECT - Tag Pinning (More Maintainable):

steps:
  - name: Checkout code
    uses: actions/checkout@v4

  - name: Set up Node.js
    uses: actions/setup-node@v4
    with:
      node-version: '20'

WRONG:

steps:
  - name: Checkout code
    uses: actions/checkout@main # Unstable, could break unexpectedly

  - name: Set up Node.js
    uses: actions/setup-node@latest # Not a valid reference

Pinning Strategy Decision Matrix:

FactorSHA PinningTag Pinning
SecurityBest (immutable)Good (can be moved)
MaintenanceHigher (manual updates)Lower (automatic minor updates)
ReadabilityLower (hash not meaningful)Higher (version clear)
AuditabilityBest (exact code version)Good (if using comment)

Recommended Approach:

  1. Use SHA pinning for security-critical workflows (production deployments)
  2. Use tag pinning with comments for most workflows
  3. Set up Dependabot to update action versions automatically
  4. Never use branch names except for actions you control

Dependabot Configuration:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: weekly
    commit-message:
      prefix: 'chore(deps)'

Workflow Organization

Structure complex workflows for maintainability:

CORRECT - Job Dependencies:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Run linter
        run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Run tests
        run: npm test

  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Build application
        run: npm run build

  deploy:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to staging
        run: ./deploy.sh staging

CORRECT - Conditional Jobs:

jobs:
  changes:
    runs-on: ubuntu-latest
    outputs:
      backend: ${{ steps.filter.outputs.backend }}
      frontend: ${{ steps.filter.outputs.frontend }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Check for changes
        uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            backend:
              - 'api/**'
              - 'services/**'
            frontend:
              - 'web/**'
              - 'public/**'

  test-backend:
    needs: changes
    if: needs.changes.outputs.backend == 'true'
    runs-on: ubuntu-latest
    steps:
      - name: Test backend
        run: npm run test:backend

  test-frontend:
    needs: changes
    if: needs.changes.outputs.frontend == 'true'
    runs-on: ubuntu-latest
    steps:
      - name: Test frontend
        run: npm run test:frontend

This comprehensive guide ensures your GitHub Actions workflows are secure, efficient, and maintainable. Always prioritize clarity, security, and team collaboration when writing workflow configurations.

Stats
Stars0
Forks0
Last CommitFeb 10, 2026
Actions

Similar Skills