# Github Actions

> Master GitHub Actions workflow patterns, triggers, optimization, and best practices for CI/CD pipelines.

- Skill: `majiayu000/github-actions` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add majiayu000/github-actions`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/github-actions/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/github-actions

---


# GitHub Actions Skill

## Purpose

Master GitHub Actions workflow patterns, triggers, optimization, and best practices for CI/CD pipelines.

## Workflow Structure

### Basic Anatomy
```yaml
name: Workflow Name          # Display name in GitHub UI

on:                          # Trigger events
  push:
    branches: [main]

permissions:                 # Required permissions (use minimal)
  contents: read
  pull-requests: write

jobs:                        # One or more jobs
  job-id:                   # Unique job identifier
    runs-on: ubuntu-latest   # Runner environment
    steps:                   # Sequential steps
      - name: Step name
        run: command
```

## Common Triggers

### Push Events
```yaml
# Push to any branch
on: push

# Push to specific branches
on:
  push:
    branches:
      - main
      - develop

# Push to branches matching pattern
on:
  push:
    branches:
      - 'releases/**'
      - 'feature/*'

# Exclude branches
on:
  push:
    branches-ignore:
      - 'docs/**'
```

### Pull Request Events
```yaml
# Any pull request
on: pull_request

# PR to specific branches
on:
  pull_request:
    branches: [main]

# PR types
on:
  pull_request:
    types: [opened, synchronize, reopened, closed]
```

### Manual and Scheduled Triggers
```yaml
# Manual trigger (workflow_dispatch)
on: workflow_dispatch

# With inputs
on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy to'
        required: true
        type: choice
        options:
          - dev
          - staging
          - production

# Scheduled (cron)
on:
  schedule:
    - cron: '0 0 * * *'  # Daily at midnight UTC
    - cron: '0 */6 * * *'  # Every 6 hours
```

## Optimization Patterns

### Dependency Caching
```yaml
# Automatic npm caching (recommended)
- uses: actions/setup-node@v3
  with:
    node-version: '18'
    cache: 'npm'

# Manual caching (more control)
- uses: actions/cache@v3
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-
```

### Build Artifact Caching
```yaml
- name: Cache build
  uses: actions/cache@v3
  with:
    path: build/
    key: build-${{ github.sha }}

- name: Upload artifacts
  uses: actions/upload-artifact@v3
  with:
    name: build-files
    path: build/
    retention-days: 7
```

### Parallel Jobs
```yaml
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - run: npm test

  build:
    runs-on: ubuntu-latest
    needs: [lint, test]  # Wait for lint and test
    steps:
      - run: npm run build
```

## Conditional Execution

### If Conditions
```yaml
# Run only on push to main
- name: Deploy
  if: github.ref == 'refs/heads/main'
  run: npm run deploy

# Run only on pull requests
- name: Preview
  if: github.event_name == 'pull_request'
  run: npm run preview

# Run only on merged PR
- name: Cleanup
  if: github.event.pull_request.merged == true
  run: ./cleanup.sh

# Complex conditions
- name: Deploy to prod
  if: |
    github.ref == 'refs/heads/main' &&
    github.event_name == 'push' &&
    !contains(github.event.head_commit.message, '[skip ci]')
  run: deploy
```

## Environment Variables and Secrets

### Using Secrets
```yaml
env:
  # Global environment variables
  NODE_VERSION: '18'
  API_KEY: ${{ secrets.API_KEY }}

jobs:
  deploy:
    steps:
      - name: Deploy with secrets
        env:
          # Step-specific environment variables
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
          API_URL: ${{ secrets.API_URL }}
        run: |
          echo "Deploying..."
          firebase deploy --token $FIREBASE_TOKEN
```

### GitHub Context Variables
```yaml
# Common context variables
${{ github.repository }}     # owner/repo
${{ github.ref }}            # refs/heads/main
${{ github.sha }}            # commit SHA
${{ github.event_name }}     # push, pull_request, etc.
${{ github.actor }}          # User who triggered workflow
${{ github.run_id }}         # Unique run ID

# Pull request specific
${{ github.event.pull_request.number }}
${{ github.event.pull_request.title }}
${{ github.head_ref }}       # PR source branch
${{ github.base_ref }}       # PR target branch
```

## Matrix Builds

### Test Multiple Versions
```yaml
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: [16, 18, 20]
    steps:
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm test
```

### Matrix with Exclusions
```yaml
strategy:
  matrix:
    os: [ubuntu-latest, windows-latest]
    node: [16, 18, 20]
    exclude:
      - os: windows-latest
        node: 16  # Skip Windows + Node 16
```

## Common Patterns

### React App CI
```yaml
name: React CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Test
        run: npm test -- --coverage --watchAll=false
        env:
          CI: true

      - name: Build
        run: npm run build
```

### Deployment with Approval
```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://myapp.com
    steps:
      - run: npm run deploy
```

### Conditional Deployment
```yaml
jobs:
  deploy:
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production
        run: ./deploy.sh
```

## Performance Optimization

### Fast npm Install
```yaml
# ✅ Use npm ci (faster, more reliable)
- run: npm ci

# ❌ Don't use npm install in CI
- run: npm install  # Slower, non-deterministic
```

### Caching Strategy
```yaml
# Cache node_modules
- uses: actions/cache@v3
  with:
    path: node_modules
    key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}

# Cache .npm directory (smaller, faster)
- uses: actions/cache@v3
  with:
    path: ~/.npm
    key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
```

### Skip Unnecessary Runs
```yaml
# Skip CI for docs-only changes
on:
  push:
    paths-ignore:
      - '**.md'
      - 'docs/**'

# Run only for specific paths
on:
  push:
    paths:
      - 'src/**'
      - 'package*.json'
```

## Best Practices

### ✅ Do
- Use `npm ci` not `npm install`
- Cache dependencies
- Set specific Node version
- Use latest action versions (`@v3`, `@v4`)
- Set minimal permissions
- Quote strings with special characters
- Use meaningful job and step names

### ❌ Don't
- Hardcode secrets in workflow files
- Use deprecated actions
- Skip dependency caching
- Use `npm install` in CI
- Leave complex workflows undocumented
- Ignore workflow failures

## Debugging Workflows

```bash
# View workflow runs
gh run list --workflow=workflow-name.yml

# View specific run with logs
gh run view RUN_ID --log

# Re-run failed workflow
gh run rerun RUN_ID

# Download logs
gh run download RUN_ID

# Watch workflow in real-time
gh run watch
```

## Common Issues

**Issue**: Workflow not triggering
**Solution**: Check trigger conditions, branch names, path filters

**Issue**: `npm ci` fails
**Solution**: Ensure package-lock.json is committed, check Node version

**Issue**: Secrets not available
**Solution**: Verify secret exists (`gh secret list`), check spelling

**Issue**: Slow builds
**Solution**: Enable caching, use `npm ci`, parallelize jobs

## Project Usage

Music-app workflows:
- `trunk-based-checks.yml`: PR validation, testing, build verification
- `firebase-hosting-merge.yml`: Production deployment (manual trigger)
- `firebase-hosting-pull-request.yml`: PR preview (manual trigger)

All workflows use Node 18, npm caching, and trunk-based development checks.

