Skip to content
Back to skills

Github Actions

ASecurity

Use this skill when authoring or debugging GitHub Actions workflows (.github/workflows/*.yml) — e.g. "add CI for this Drupal project on GitHub", "run phpcs/phpstan/phpunit on pull requests", "cache composer dependencies", "deploy over SSH when main is pushed", "why didn't my workflow trigger", or anything involving jobs, matrices, runners, secrets, or reusable workflows.

  • 16 stars
  • 0 votes
  • 0 copies
  • 6 views
  • Added August 31, 2026
code-qualityphpsqldebugginggitdatabase

Security analysis

A100/100

Scanned August 31, 2026

npx -y skills add siva01c/claude-plugins --skill github-actions --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Github Actions?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Github Actions
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/siva01c-github-actions/badge)](https://www.skillsdirectory.com/skills/siva01c-github-actions)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: github-actions
description: >
  Use this skill when authoring or debugging GitHub Actions workflows
  (.github/workflows/*.yml) — e.g. "add CI for this Drupal project on
  GitHub", "run phpcs/phpstan/phpunit on pull requests", "cache composer
  dependencies", "deploy over SSH when main is pushed", "why didn't my
  workflow trigger", or anything involving jobs, matrices, runners, secrets,
  or reusable workflows.
---

# GitHub Actions Skill

GitHub Actions runs workflows from `.github/workflows/*.yml`. This skill
covers workflow authoring with a Drupal project as the working example:
linting, static analysis, PHPUnit with a database service, and SSH/drush
deployment.

---

## Workflow anatomy

```yaml
name: CI

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read            # least privilege — grant more only per job

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true  # newer push cancels the outdated run

jobs:
  phpcs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          tools: composer:v2
      - uses: actions/cache@v4
        with:
          path: ~/.composer/cache
          key: composer-${{ hashFiles('composer.lock') }}
          restore-keys: composer-
      - run: composer install --no-progress
      - run: vendor/bin/phpcs --standard=Drupal,DrupalPractice web/modules/custom
```

Key defaults to always set:

- **`permissions:`** — the implicit token is powerful; start from
  `contents: read` and widen per job only when needed.
- **`concurrency:`** — avoids burning runner minutes on superseded pushes.

---

## PHPUnit with a database service

```yaml
  phpunit:
    runs-on: ubuntu-latest
    services:
      db:
        image: mariadb:11.4
        env:
          MARIADB_DATABASE: drupal_test
          MARIADB_ROOT_PASSWORD: root
        ports: ['3306:3306']
        options: >-
          --health-cmd="healthcheck.sh --connect --innodb_initialized"
          --health-interval=10s --health-timeout=5s --health-retries=5
    env:
      SIMPLETEST_DB: mysql://root:root@127.0.0.1:3306/drupal_test
      SIMPLETEST_BASE_URL: http://localhost
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          extensions: gd, pdo_mysql
      - run: composer install --no-progress
      - run: vendor/bin/phpunit -c web/core/phpunit.xml.dist web/modules/custom
```

`services:` containers get health-checked before steps run — no manual
wait loops. From the runner they are reachable on `127.0.0.1:<mapped port>`.

### Matrix builds

```yaml
    strategy:
      matrix:
        php: ['8.3', '8.4']
    steps:
      - uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
```

---

## Deployment job

```yaml
  deploy:
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    needs: [phpcs, phpunit]
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://www.example.com
    steps:
      - uses: webfactory/ssh-agent@v0.9.0
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
      - run: |
          ssh -o StrictHostKeyChecking=accept-new deploy@prod.example.com \
            "cd /var/www/site && git pull --ff-only \
             && composer install --no-dev --optimize-autoloader \
             && vendor/bin/drush deploy -y"
```

- `needs:` gates deployment on green checks.
- `environment:` enables required reviewers and deployment history
  (Settings → Environments) — use it for production approval gates.
- Secrets live in repo/environment settings, referenced via
  `${{ secrets.NAME }}`. For cloud providers prefer **OIDC**
  (`permissions: id-token: write` + the provider's auth action) over
  long-lived keys.

---

## Reuse

- **Reusable workflow** — a whole workflow callable from others:

  ```yaml
  # .github/workflows/drupal-tests.yml
  on:
    workflow_call:
      inputs:
        php-version: { type: string, default: '8.3' }
  ```

  ```yaml
  # caller
  jobs:
    tests:
      uses: ./.github/workflows/drupal-tests.yml
      with:
        php-version: '8.4'
  ```

- **Composite action** — a reusable step sequence in
  `.github/actions/<name>/action.yml` (e.g. "setup PHP + composer install
  with cache") to deduplicate job boilerplate.

---

## Troubleshooting

| Symptom | Fix |
|---|---|
| Workflow doesn't trigger | Check `on:` filters — `branches:`/`paths:` excluded the ref, or push was from a workflow using the default token (no recursive triggers) |
| `Permission denied` on push/comment from job | Widen `permissions:` for that job (e.g. `pull-requests: write`) |
| Composer cache never hits | Key on `hashFiles('composer.lock')` and add a `restore-keys:` prefix fallback |
| PHPUnit can't reach DB | Use `127.0.0.1` with the mapped port, not the service name (service names only resolve in container jobs) |
| Deploy ran before tests finished | Add the test jobs to `needs:` of the deploy job |
| Two runs per PR push | `push:` + `pull_request:` both trigger — restrict `push:` to `branches: [main]` |

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.