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.
Scanned 8/31/2026
Install to Claude Code
npx -y skills add siva01c/claude-plugins --skill github-actions --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Github Actions?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/siva01c-github-actions)More formats (shields.io, HTML) on the badges page.
---
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]` |
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!