Back to skills
SkillHub ClubWrite Technical DocsFull StackDevOpsTech Writer

writing-github-actions

Write GitHub Actions workflows with proper syntax, reusable workflows, composite actions, matrix builds, caching, and security best practices. Use when creating CI/CD workflows for GitHub-hosted projects or automating GitHub repository tasks.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
318
Hot score
99
Updated
March 20, 2026
Overall rating
C3.8
Composite score
3.8
Best-practice grade
B75.6

Install command

npx @skill-hub/cli install ancoleman-ai-design-components-writing-github-actions

Repository

ancoleman/ai-design-components

Skill path: skills/writing-github-actions

Write GitHub Actions workflows with proper syntax, reusable workflows, composite actions, matrix builds, caching, and security best practices. Use when creating CI/CD workflows for GitHub-hosted projects or automating GitHub repository tasks.

Open repository

Best for

Primary workflow: Write Technical Docs.

Technical facets: Full Stack, DevOps, Tech Writer, Security.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: ancoleman.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install writing-github-actions into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/ancoleman/ai-design-components before adding writing-github-actions to shared team environments
  • Use writing-github-actions for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: writing-github-actions
description: Write GitHub Actions workflows with proper syntax, reusable workflows, composite actions, matrix builds, caching, and security best practices. Use when creating CI/CD workflows for GitHub-hosted projects or automating GitHub repository tasks.
---

# Writing GitHub Actions

Create GitHub Actions workflows for CI/CD pipelines, automated testing, deployments, and repository automation using YAML-based configuration with native GitHub integration.

## Purpose

GitHub Actions is the native CI/CD platform for GitHub repositories. This skill covers workflow syntax, triggers, job orchestration, reusable patterns, optimization techniques, and security practices specific to GitHub Actions.

**Core Focus:**
- Workflow YAML syntax and structure
- Reusable workflows and composite actions
- Matrix builds and parallel execution
- Caching and optimization strategies
- Secrets management and OIDC authentication
- Concurrency control and artifact management

**Not Covered:**
- CI/CD pipeline design strategy → See `building-ci-pipelines`
- GitOps deployment patterns → See `gitops-workflows`
- Infrastructure as code → See `infrastructure-as-code`
- Testing frameworks → See `testing-strategies`

## When to Use This Skill

Trigger this skill when:
- Creating CI/CD workflows for GitHub repositories
- Automating tests, builds, and deployments via GitHub Actions
- Setting up reusable workflows across multiple repositories
- Optimizing workflow performance with caching and parallelization
- Implementing security best practices for GitHub Actions
- Troubleshooting GitHub Actions YAML syntax or behavior

## Workflow Fundamentals

### Basic Workflow Structure

```yaml
name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm test
```

**Key Components:**
- `name`: Workflow display name
- `on`: Trigger events (push, pull_request, schedule, workflow_dispatch)
- `jobs`: Job definitions (run in parallel by default)
- `runs-on`: Runner type (ubuntu-latest, windows-latest, macos-latest)
- `steps`: Sequential operations (uses actions or run commands)

### Common Triggers

```yaml
# Code events
on:
  push:
    branches: [main, develop]
    paths: ['src/**']
  pull_request:
    types: [opened, synchronize, reopened]

# Manual trigger
on:
  workflow_dispatch:
    inputs:
      environment:
        type: choice
        options: [dev, staging, production]

# Scheduled
on:
  schedule:
    - cron: '0 2 * * *'  # Daily at 2 AM UTC
```

For complete trigger reference, see `references/triggers-events.md`.

## Decision Frameworks

### Reusable Workflow vs Composite Action

**Use Reusable Workflow when:**
- Standardizing entire CI/CD jobs across repositories
- Need complete job replacement with inputs/outputs
- Want secrets to inherit by default
- Orchestrating multiple steps with job-level configuration

**Use Composite Action when:**
- Packaging 5-20 step sequences for reuse
- Need step-level abstraction within jobs
- Want to distribute via marketplace or private repos
- Require local file access without artifacts

| Feature | Reusable Workflow | Composite Action |
|---------|------------------|------------------|
| Scope | Complete job | Step sequence |
| Trigger | `workflow_call` | `uses:` in step |
| Secrets | Inherit by default | Must pass explicitly |
| File Sharing | Requires artifacts | Same runner/workspace |

For detailed patterns, see `references/reusable-workflows.md` and `references/composite-actions.md`.

### Caching Strategy

**Use Built-in Setup Action Caching (Recommended):**
```yaml
- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'  # or 'yarn', 'pnpm'
```

Available for: Node.js, Python (pip), Java (maven/gradle), .NET, Go

**Use Manual Caching when:**
- Need custom cache keys
- Caching build outputs or non-standard paths
- Implementing multi-layer cache strategies

```yaml
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}
    restore-keys: ${{ runner.os }}-deps-
```

For optimization techniques, see `references/caching-strategies.md`.

### Self-Hosted vs GitHub-Hosted Runners

**Use GitHub-Hosted Runners when:**
- Standard build environments sufficient
- No private network access required
- Within budget or free tier limits

**Use Self-Hosted Runners when:**
- Need specific hardware (GPU, ARM, high memory)
- Require private network/VPN access
- High usage volume (cost optimization)
- Custom software must be pre-installed

## Common Patterns

### Multi-Job Workflow with Dependencies

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v5
        with:
          name: dist
      - run: npm test

  deploy:
    needs: [build, test]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/download-artifact@v5
      - run: ./deploy.sh
```

**Key Elements:**
- `needs:` creates job dependencies (sequential execution)
- Artifacts pass data between jobs
- `if:` enables conditional execution
- `environment:` enables protection rules and environment secrets

### Matrix Builds

```yaml
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: [18, 20, 22]
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm test
```

Result: 9 jobs (3 OS × 3 Node versions)

For advanced matrix patterns, see `examples/matrix-build.yml`.

### Concurrency Control

```yaml
# Cancel in-progress runs on new push
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

# Single deployment per environment
jobs:
  deploy:
    concurrency:
      group: production-deployment
      cancel-in-progress: false
    steps: [...]
```

## Reusable Workflows

### Defining a Reusable Workflow

File: `.github/workflows/reusable-build.yml`

```yaml
name: Reusable Build
on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '20'
    secrets:
      NPM_TOKEN:
        required: false
    outputs:
      artifact-name:
        value: ${{ jobs.build.outputs.artifact }}

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      artifact: build-output
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
      - run: npm ci && npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/
```

### Calling a Reusable Workflow

```yaml
jobs:
  build:
    uses: ./.github/workflows/reusable-build.yml
    with:
      node-version: '20'
    secrets: inherit  # Same org only
```

For complete reusable workflow guide, see `references/reusable-workflows.md`.

## Composite Actions

### Defining a Composite Action

File: `.github/actions/setup-project/action.yml`

```yaml
name: 'Setup Project'
description: 'Install dependencies and setup environment'

inputs:
  node-version:
    description: 'Node.js version'
    default: '20'

outputs:
  cache-hit:
    value: ${{ steps.cache.outputs.cache-hit }}

runs:
  using: "composite"
  steps:
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'

    - id: cache
      uses: actions/cache@v4
      with:
        path: node_modules
        key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}

    - if: steps.cache.outputs.cache-hit != 'true'
      shell: bash
      run: npm ci
```

**Key Requirements:**
- `runs.using: "composite"` marks action type
- `shell:` required for all `run` steps
- Access inputs via `${{ inputs.name }}`

### Using a Composite Action

```yaml
steps:
  - uses: actions/checkout@v5
  - uses: ./.github/actions/setup-project
    with:
      node-version: '20'
  - run: npm run build
```

For detailed composite action patterns, see `references/composite-actions.md`.

## Security Best Practices

### Secrets Management

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production  # Uses environment secrets
    steps:
      - env:
          API_KEY: ${{ secrets.API_KEY }}
        run: ./deploy.sh
```

### OIDC Authentication (No Long-Lived Credentials)

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # Required for OIDC
      contents: read
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
          aws-region: us-east-1
      - run: aws s3 sync ./dist s3://my-bucket
```

### Minimal Permissions

```yaml
# Workflow-level
permissions:
  contents: read
  pull-requests: write

# Job-level
jobs:
  deploy:
    permissions:
      contents: write
      deployments: write
    steps: [...]
```

### Action Pinning

```yaml
# Pin to commit SHA (not tags)
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608  # v5.0.0
```

**Enable Dependabot:**

File: `.github/dependabot.yml`

```yaml
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
```

For comprehensive security guide, see `references/security-practices.md`.

## Optimization Techniques

Use built-in caching in setup actions (`cache: 'npm'`), run independent jobs in parallel, add conditional execution with `if:`, and minimize checkout depth (`fetch-depth: 1`).

For detailed optimization strategies, see `references/caching-strategies.md`.

## Context Variables

Common contexts: `github.*`, `secrets.*`, `inputs.*`, `matrix.*`, `runner.*`

```yaml
- run: echo "Branch: ${{ github.ref }}, Event: ${{ github.event_name }}"
```

For complete syntax reference, see `references/workflow-syntax.md`.

## Progressive Disclosure

### Detailed References

For comprehensive coverage of specific topics:

- **references/workflow-syntax.md** - Complete YAML syntax reference
- **references/reusable-workflows.md** - Advanced reusable workflow patterns
- **references/composite-actions.md** - Composite action deep dive
- **references/caching-strategies.md** - Optimization and caching techniques
- **references/security-practices.md** - Comprehensive security guide
- **references/triggers-events.md** - All trigger types and event filters
- **references/marketplace-actions.md** - Recommended actions catalog

### Working Examples

Complete workflow templates ready to use:

- **examples/basic-ci.yml** - Simple CI workflow
- **examples/matrix-build.yml** - Matrix strategy examples
- **examples/reusable-deploy.yml** - Reusable deployment workflow
- **examples/composite-setup/** - Composite action template
- **examples/monorepo-workflow.yml** - Monorepo with path filters
- **examples/security-scan.yml** - Security scanning workflow

### Validation Scripts

- **scripts/validate-workflow.sh** - Validate YAML syntax

## Related Skills

- `building-ci-pipelines` - CI/CD pipeline design strategy
- `gitops-workflows` - GitOps deployment patterns
- `infrastructure-as-code` - Terraform/Pulumi integration
- `testing-strategies` - Test frameworks and coverage
- `security-hardening` - SAST/DAST tools
- `git-workflows` - Understanding branches and PRs


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/triggers-events.md

```markdown
# GitHub Actions Triggers and Events

Complete reference for workflow triggers, event types, and activity filters.

## Event Categories

### Code Events
- `push` - Code pushed to repository
- `pull_request` - Pull request activity
- `pull_request_target` - Pull request targeting base branch (safe for secrets)
- `create` - Branch or tag created
- `delete` - Branch or tag deleted

### Repository Events
- `release` - Release published, created, edited
- `watch` - Repository starred
- `fork` - Repository forked
- `issues` - Issue activity
- `issue_comment` - Issue/PR comment activity
- `discussion` - Discussion activity

### Workflow Events
- `workflow_dispatch` - Manual trigger
- `workflow_call` - Reusable workflow
- `workflow_run` - Triggered by another workflow
- `repository_dispatch` - Webhook trigger

### Scheduled Events
- `schedule` - Cron-based trigger

### Deployment Events
- `deployment` - Deployment created
- `deployment_status` - Deployment status changed

For complete syntax examples, see `workflow-syntax.md`.

```

### references/reusable-workflows.md

```markdown
# Reusable Workflows Guide

Advanced patterns and best practices for creating and using reusable workflows in GitHub Actions.

## Table of Contents

1. [Overview](#overview)
2. [Creating Reusable Workflows](#creating-reusable-workflows)
3. [Calling Reusable Workflows](#calling-reusable-workflows)
4. [Passing Data](#passing-data)
5. [Matrix Strategies with Reusable Workflows](#matrix-strategies-with-reusable-workflows)
6. [Nested Reusable Workflows](#nested-reusable-workflows)
7. [Best Practices](#best-practices)
8. [Common Patterns](#common-patterns)

---

## Overview

Reusable workflows enable job-level reuse across repositories and workflows. They standardize CI/CD processes and reduce duplication.

**Key Benefits:**
- Centralize CI/CD logic in single location
- Standardize workflows across organization
- Version workflows independently
- Reduce maintenance burden

**When to Use:**
- Standardizing build/test/deploy jobs
- Enforcing organization policies
- Sharing workflows across repositories
- Complex multi-step jobs with configuration

**Limitations:**
- Maximum 10 levels of nesting
- Maximum 50 workflow calls per run
- Cannot call reusable workflows from same repository in different directory
- Secrets must be explicitly passed or inherited

---

## Creating Reusable Workflows

### Basic Structure

File: `.github/workflows/reusable-build.yml`

```yaml
name: Reusable Build

on:
  workflow_call:
    inputs:
      # Define inputs here
    secrets:
      # Define secrets here
    outputs:
      # Define outputs here

jobs:
  # Job definitions
```

### With Inputs

```yaml
name: Reusable Build

on:
  workflow_call:
    inputs:
      node-version:
        description: 'Node.js version to use'
        required: false
        type: string
        default: '20'
      build-command:
        description: 'Build command to run'
        required: false
        type: string
        default: 'npm run build'
      working-directory:
        description: 'Working directory'
        required: false
        type: string
        default: '.'

jobs:
  build:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ${{ inputs.working-directory }}
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: 'npm'

      - run: npm ci

      - run: ${{ inputs.build-command }}
```

**Input Types:**
- `string` - Text value
- `number` - Numeric value
- `boolean` - true/false
- `choice` - Predefined options (not available in workflow_call)

### With Secrets

```yaml
name: Reusable Deploy

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
    secrets:
      api-key:
        required: true
      npm-token:
        required: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v5

      - name: Deploy
        env:
          API_KEY: ${{ secrets.api-key }}
          NPM_TOKEN: ${{ secrets.npm-token }}
        run: ./deploy.sh
```

### With Outputs

```yaml
name: Reusable Build with Outputs

on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '20'
    outputs:
      artifact-name:
        description: "Name of the uploaded artifact"
        value: ${{ jobs.build.outputs.artifact }}
      version:
        description: "Version number"
        value: ${{ jobs.build.outputs.version }}

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      artifact: ${{ steps.upload.outputs.artifact-name }}
      version: ${{ steps.version.outputs.version }}
    steps:
      - uses: actions/checkout@v5

      - id: version
        run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT

      - run: npm run build

      - id: upload
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ steps.version.outputs.version }}
          path: dist/
```

---

## Calling Reusable Workflows

### Same Repository

```yaml
name: CI

on: [push]

jobs:
  build:
    uses: ./.github/workflows/reusable-build.yml
    with:
      node-version: '20'
      build-command: 'npm run build:prod'
```

**Path Requirements:**
- Must start with `./`
- Must reference `.github/workflows/` directory
- Use relative path from repository root

### Different Repository (Same Organization)

```yaml
name: CI

on: [push]

jobs:
  build:
    uses: my-org/shared-workflows/.github/workflows/reusable-build.yml@v1
    with:
      node-version: '20'
    secrets: inherit
```

**Reference Format:** `{owner}/{repo}/{path}@{ref}`

**Refs:**
- Tag: `@v1`, `@v1.2.3`
- Branch: `@main`, `@develop`
- Commit SHA: `@abc123...` (most secure)

### Different Repository (Public)

```yaml
jobs:
  build:
    uses: other-org/public-workflows/.github/workflows/build.yml@v1
    with:
      node-version: '20'
    secrets:
      npm-token: ${{ secrets.NPM_TOKEN }}
```

**Note:** Cannot use `secrets: inherit` for external organizations

---

## Passing Data

### Passing Inputs

```yaml
jobs:
  build:
    uses: ./.github/workflows/reusable-build.yml
    with:
      node-version: '20'
      build-command: 'npm run build'
      enable-tests: true
```

### Passing Secrets (Explicit)

```yaml
jobs:
  deploy:
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: production
    secrets:
      api-key: ${{ secrets.PROD_API_KEY }}
      npm-token: ${{ secrets.NPM_TOKEN }}
```

### Passing All Secrets (Inherit)

```yaml
jobs:
  deploy:
    uses: my-org/workflows/.github/workflows/deploy.yml@v1
    with:
      environment: production
    secrets: inherit
```

**Requirements for `secrets: inherit`:**
- Same organization or enterprise
- Caller workflow has access to secrets

### Using Outputs from Reusable Workflows

```yaml
jobs:
  build:
    uses: ./.github/workflows/reusable-build.yml
    with:
      node-version: '20'

  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: echo "Artifact: ${{ needs.build.outputs.artifact-name }}"
      - run: echo "Version: ${{ needs.build.outputs.version }}"

      - uses: actions/download-artifact@v5
        with:
          name: ${{ needs.build.outputs.artifact-name }}
```

---

## Matrix Strategies with Reusable Workflows

### Matrix in Caller Workflow

```yaml
jobs:
  multi-platform-build:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: [18, 20, 22]
    uses: ./.github/workflows/build.yml
    with:
      os: ${{ matrix.os }}
      node-version: ${{ matrix.node }}
```

**Reusable Workflow:**

```yaml
on:
  workflow_call:
    inputs:
      os:
        required: true
        type: string
      node-version:
        required: true
        type: string

jobs:
  build:
    runs-on: ${{ inputs.os }}
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
      - run: npm run build
```

### Matrix in Reusable Workflow

```yaml
# Reusable workflow with internal matrix
on:
  workflow_call:
    inputs:
      environments:
        required: true
        type: string  # JSON array

jobs:
  deploy:
    strategy:
      matrix:
        environment: ${{ fromJSON(inputs.environments) }}
    runs-on: ubuntu-latest
    environment: ${{ matrix.environment }}
    steps:
      - run: ./deploy.sh ${{ matrix.environment }}
```

**Calling:**

```yaml
jobs:
  multi-env-deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      environments: '["dev", "staging", "production"]'
```

---

## Nested Reusable Workflows

### Two-Level Nesting

**Level 1: Base Workflow**

File: `.github/workflows/base-build.yml`

```yaml
name: Base Build

on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '20'
    outputs:
      artifact-name:
        value: ${{ jobs.build.outputs.artifact }}

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      artifact: build-output
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
      - run: npm ci && npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/
```

**Level 2: Extended Workflow**

File: `.github/workflows/build-and-test.yml`

```yaml
name: Build and Test

on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '20'

jobs:
  build:
    uses: ./.github/workflows/base-build.yml
    with:
      node-version: ${{ inputs.node-version }}

  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v5
        with:
          name: ${{ needs.build.outputs.artifact-name }}
      - run: npm test
```

**Level 3: Main Workflow**

```yaml
name: CI

on: [push]

jobs:
  ci:
    uses: ./.github/workflows/build-and-test.yml
    with:
      node-version: '20'
```

### Limits

- Maximum nesting: 10 levels
- Maximum workflow calls: 50 per run
- Each level counts toward limits

---

## Best Practices

### 1. Version Reusable Workflows

**Use Semantic Versioning:**

```yaml
# Pin to major version (recommended)
uses: my-org/workflows/.github/workflows/build.yml@v1

# Pin to specific version (most stable)
uses: my-org/workflows/.github/workflows/[email protected]

# Pin to commit SHA (most secure)
uses: my-org/workflows/.github/workflows/build.yml@abc123...
```

**Create Tags:**

```bash
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0

# Update major version tag
git tag -fa v1 -m "Update v1 to v1.0.0"
git push origin v1 --force
```

### 2. Document Inputs and Outputs

```yaml
on:
  workflow_call:
    inputs:
      node-version:
        description: |
          Node.js version to use for build.
          Supports: 18, 20, 22
          Default: 20
        required: false
        type: string
        default: '20'
    outputs:
      artifact-name:
        description: |
          Name of the uploaded build artifact.
          Use with actions/download-artifact to retrieve.
        value: ${{ jobs.build.outputs.artifact }}
```

### 3. Provide Sensible Defaults

```yaml
inputs:
  node-version:
    type: string
    default: '20'
  build-command:
    type: string
    default: 'npm run build'
  test-command:
    type: string
    default: 'npm test'
  working-directory:
    type: string
    default: '.'
```

### 4. Use Permissions Explicitly

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps: [...]
```

### 5. Handle Errors Gracefully

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - name: Build
        run: npm run build
        continue-on-error: ${{ inputs.allow-build-failure || false }}

      - if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: build-logs
          path: logs/
```

### 6. Use Concurrency Controls

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    concurrency:
      group: deploy-${{ inputs.environment }}
      cancel-in-progress: false
    steps: [...]
```

---

## Common Patterns

### Pattern 1: Standardized Build

```yaml
name: Standard Node.js Build

on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '20'
      package-manager:
        type: string
        default: 'npm'
    outputs:
      artifact-name:
        value: ${{ jobs.build.outputs.artifact }}

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      artifact: build-${{ github.sha }}
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: ${{ inputs.package-manager }}

      - name: Install dependencies
        run: |
          if [ "${{ inputs.package-manager }}" = "npm" ]; then
            npm ci
          elif [ "${{ inputs.package-manager }}" = "yarn" ]; then
            yarn install --frozen-lockfile
          elif [ "${{ inputs.package-manager }}" = "pnpm" ]; then
            pnpm install --frozen-lockfile
          fi

      - run: ${{ inputs.package-manager }} run build

      - uses: actions/upload-artifact@v4
        with:
          name: build-${{ github.sha }}
          path: dist/
```

### Pattern 2: Multi-Environment Deploy

```yaml
name: Deploy to Environment

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      version:
        required: true
        type: string
    secrets:
      aws-access-key-id:
        required: true
      aws-secret-access-key:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: ${{ inputs.environment }}
      url: https://${{ inputs.environment }}.example.com
    concurrency:
      group: deploy-${{ inputs.environment }}
      cancel-in-progress: false
    steps:
      - uses: actions/checkout@v5

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.aws-access-key-id }}
          aws-secret-access-key: ${{ secrets.aws-secret-access-key }}
          aws-region: us-east-1

      - name: Deploy
        run: |
          echo "Deploying version ${{ inputs.version }} to ${{ inputs.environment }}"
          ./deploy.sh
```

### Pattern 3: Test Matrix

```yaml
name: Test Matrix

on:
  workflow_call:
    inputs:
      node-versions:
        type: string
        default: '["18", "20", "22"]'
      os-list:
        type: string
        default: '["ubuntu-latest"]'

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: ${{ fromJSON(inputs.os-list) }}
        node: ${{ fromJSON(inputs.node-versions) }}
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'

      - run: npm ci
      - run: npm test

      - if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results-${{ matrix.os }}-${{ matrix.node }}
          path: test-results/
```

### Pattern 4: Conditional Jobs

```yaml
name: CI with Optional Deploy

on:
  workflow_call:
    inputs:
      run-tests:
        type: boolean
        default: true
      run-lint:
        type: boolean
        default: true
      deploy:
        type: boolean
        default: false
      environment:
        type: string
        default: 'dev'

jobs:
  lint:
    if: inputs.run-lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: npm run lint

  test:
    if: inputs.run-tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: npm test

  deploy:
    if: inputs.deploy
    needs: [lint, test]
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - run: ./deploy.sh
```

### Pattern 5: Composite Build and Release

```yaml
name: Build and Release

on:
  workflow_call:
    inputs:
      create-release:
        type: boolean
        default: false
      version:
        type: string
        required: true
    secrets:
      github-token:
        required: true

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      artifact-name: build-${{ inputs.version }}
    steps:
      - uses: actions/checkout@v5
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: build-${{ inputs.version }}
          path: dist/

  release:
    if: inputs.create-release
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/download-artifact@v5
        with:
          name: ${{ needs.build.outputs.artifact-name }}

      - name: Create Release
        uses: softprops/action-gh-release@v1
        with:
          tag_name: v${{ inputs.version }}
          files: dist/*
        env:
          GITHUB_TOKEN: ${{ secrets.github-token }}
```

---

## Troubleshooting

### Common Issues

**1. Secrets Not Available**

**Problem:** Reusable workflow cannot access secrets

**Solution:**
```yaml
# Caller must pass secrets explicitly or use inherit
jobs:
  deploy:
    uses: ./.github/workflows/deploy.yml
    secrets: inherit  # Or pass explicitly
```

**2. Cannot Reference Local Reusable Workflow**

**Problem:** `uses: ./.github/workflows/build.yml` not found

**Solution:**
- Ensure workflow file exists in `.github/workflows/` directory
- Use `./` prefix for same repository
- Check file path is relative to repository root

**3. Matrix Evaluation Errors**

**Problem:** Matrix values from inputs not working

**Solution:**
```yaml
# Pass as JSON string
strategy:
  matrix:
    version: ${{ fromJSON(inputs.versions) }}

# Caller provides JSON array
with:
  versions: '["18", "20", "22"]'
```

**4. Outputs Not Available**

**Problem:** Cannot access outputs from reusable workflow

**Solution:**
```yaml
# Reusable workflow must define outputs
on:
  workflow_call:
    outputs:
      result:
        value: ${{ jobs.build.outputs.result }}

# Job must export outputs
jobs:
  build:
    outputs:
      result: ${{ steps.step-id.outputs.result }}
```

---

## Migration from Regular Workflows

### Before (Duplicated Workflow)

```yaml
# repo-a/.github/workflows/ci.yml
name: CI
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
      - run: npm ci && npm run build

# repo-b/.github/workflows/ci.yml
name: CI
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
      - run: npm ci && npm run build
```

### After (Reusable Workflow)

**Shared Workflow:**

```yaml
# shared-workflows/.github/workflows/node-build.yml
name: Node.js Build
on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '20'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
      - run: npm ci && npm run build
```

**Caller Workflows:**

```yaml
# repo-a/.github/workflows/ci.yml
name: CI
on: [push]
jobs:
  build:
    uses: org/shared-workflows/.github/workflows/node-build.yml@v1
    with:
      node-version: '20'

# repo-b/.github/workflows/ci.yml
name: CI
on: [push]
jobs:
  build:
    uses: org/shared-workflows/.github/workflows/node-build.yml@v1
    with:
      node-version: '18'
```

---

For composite actions (step-level reuse), see `composite-actions.md`.

```

### references/composite-actions.md

```markdown
# Composite Actions Guide

Step-level reusability through composite actions in GitHub Actions.

## Table of Contents

1. [Overview](#overview)
2. [Creating Composite Actions](#creating-composite-actions)
3. [Using Composite Actions](#using-composite-actions)
4. [Inputs and Outputs](#inputs-and-outputs)
5. [Best Practices](#best-practices)
6. [Common Patterns](#common-patterns)
7. [Comparison with Reusable Workflows](#comparison-with-reusable-workflows)

---

## Overview

Composite actions package multiple workflow steps into a single reusable action. They enable step-level code reuse without creating separate repositories or publishing to the marketplace.

**Key Benefits:**
- Package common step sequences
- Distribute via repository or marketplace
- Share same runner and workspace
- Support up to 10 levels of nesting

**When to Use:**
- Packaging 5-20 step sequences
- Setup/teardown operations
- Utility functions (validation, formatting)
- Organization-wide tooling standards

**vs Reusable Workflows:**
- Composite actions: Step-level reuse
- Reusable workflows: Job-level reuse

---

## Creating Composite Actions

### Basic Structure

File: `.github/actions/my-action/action.yml`

```yaml
name: 'Action Name'
description: 'What this action does'

inputs:
  # Input definitions

outputs:
  # Output definitions

runs:
  using: "composite"
  steps:
    # Step definitions
```

### Minimal Example

```yaml
name: 'Hello World'
description: 'Print hello message'

runs:
  using: "composite"
  steps:
    - run: echo "Hello from composite action"
      shell: bash
```

**Key Requirements:**
- `runs.using: "composite"` is required
- All `run` steps must specify `shell:`
- Located in `action.yml` file

### With Inputs

```yaml
name: 'Setup Project'
description: 'Install dependencies and setup environment'

inputs:
  node-version:
    description: 'Node.js version to use'
    required: false
    default: '20'
  install-command:
    description: 'Command to install dependencies'
    required: false
    default: 'npm ci'
  working-directory:
    description: 'Working directory'
    required: false
    default: '.'

runs:
  using: "composite"
  steps:
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'
        cache-dependency-path: ${{ inputs.working-directory }}/package-lock.json

    - name: Install dependencies
      shell: bash
      working-directory: ${{ inputs.working-directory }}
      run: ${{ inputs.install-command }}

    - name: Verify installation
      shell: bash
      run: node --version && npm --version
```

### With Outputs

```yaml
name: 'Get Version'
description: 'Extract version from package.json'

inputs:
  package-file:
    description: 'Path to package.json'
    required: false
    default: 'package.json'

outputs:
  version:
    description: "Version number"
    value: ${{ steps.get-version.outputs.version }}
  major:
    description: "Major version"
    value: ${{ steps.parse.outputs.major }}
  minor:
    description: "Minor version"
    value: ${{ steps.parse.outputs.minor }}

runs:
  using: "composite"
  steps:
    - id: get-version
      shell: bash
      run: |
        VERSION=$(jq -r '.version' ${{ inputs.package-file }})
        echo "version=$VERSION" >> $GITHUB_OUTPUT

    - id: parse
      shell: bash
      run: |
        VERSION="${{ steps.get-version.outputs.version }}"
        IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
        echo "major=$MAJOR" >> $GITHUB_OUTPUT
        echo "minor=$MINOR" >> $GITHUB_OUTPUT
        echo "patch=$PATCH" >> $GITHUB_OUTPUT
```

### With Script Execution

**Directory Structure:**

```
.github/actions/validate/
├── action.yml
└── scripts/
    └── validate.sh
```

**action.yml:**

```yaml
name: 'Validate Project'
description: 'Run validation checks'

inputs:
  strict-mode:
    description: 'Enable strict validation'
    required: false
    default: 'false'

runs:
  using: "composite"
  steps:
    - name: Make script executable
      shell: bash
      run: chmod +x ${{ github.action_path }}/scripts/validate.sh

    - name: Run validation
      shell: bash
      run: ${{ github.action_path }}/scripts/validate.sh
      env:
        STRICT_MODE: ${{ inputs.strict-mode }}
        ACTION_PATH: ${{ github.action_path }}
```

**scripts/validate.sh:**

```bash
#!/bin/bash
set -e

echo "Running validation..."

if [ "$STRICT_MODE" = "true" ]; then
  echo "Strict mode enabled"
  npm run lint
  npm run type-check
  npm test
else
  echo "Standard validation"
  npm run lint
fi
```

---

## Using Composite Actions

### Local Action (Same Repository)

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - name: Setup project
        uses: ./.github/actions/setup-project
        with:
          node-version: '20'
          install-command: 'npm ci'

      - run: npm run build
```

**Path Requirements:**
- Start with `./`
- Point to directory containing `action.yml`
- Relative to repository root

### External Action (Different Repository)

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - name: Setup project
        uses: my-org/shared-actions/setup-project@v1
        with:
          node-version: '20'

      - run: npm run build
```

**Reference Format:** `{owner}/{repo}/{path}@{ref}`

### Marketplace Action

```yaml
- name: Setup Java
  uses: actions/setup-java@v4
  with:
    distribution: 'temurin'
    java-version: '17'
```

### Accessing Outputs

```yaml
- name: Get version
  id: version
  uses: ./.github/actions/get-version
  with:
    package-file: 'package.json'

- name: Use version
  run: |
    echo "Version: ${{ steps.version.outputs.version }}"
    echo "Major: ${{ steps.version.outputs.major }}"
    echo "Minor: ${{ steps.version.outputs.minor }}"
```

---

## Inputs and Outputs

### Input Configuration

```yaml
inputs:
  input-name:
    description: 'Human-readable description'
    required: true|false
    default: 'default-value'
```

**Input Types:**
All inputs are strings in composite actions (no type specification like workflow_call)

**Accessing Inputs:**

```yaml
runs:
  using: "composite"
  steps:
    - run: echo "Input value: ${{ inputs.input-name }}"
      shell: bash
```

### Output Configuration

```yaml
outputs:
  output-name:
    description: 'Human-readable description'
    value: ${{ steps.step-id.outputs.value }}
```

**Setting Outputs:**

```yaml
steps:
  - id: step-id
    shell: bash
    run: echo "value=result" >> $GITHUB_OUTPUT
```

**Composite Action Output:**

```yaml
outputs:
  result:
    description: "Result value"
    value: ${{ steps.compute.outputs.result }}
```

### Environment Variables

**Passing to Steps:**

```yaml
runs:
  using: "composite"
  steps:
    - shell: bash
      env:
        INPUT_VALUE: ${{ inputs.my-input }}
        CUSTOM_VAR: custom-value
      run: |
        echo "Input: $INPUT_VALUE"
        echo "Custom: $CUSTOM_VAR"
```

**From Caller:**

Environment variables from the caller job are available:

```yaml
# Caller
jobs:
  build:
    env:
      BUILD_ENV: production
    steps:
      - uses: ./.github/actions/my-action
        # BUILD_ENV available in action

# Action can access BUILD_ENV
- run: echo $BUILD_ENV
  shell: bash
```

---

## Best Practices

### 1. Always Specify Shell

```yaml
# ❌ Bad - missing shell
- run: echo "Hello"

# ✅ Good - shell specified
- run: echo "Hello"
  shell: bash
```

**Available Shells:**
- `bash` - Bash (default on Linux/macOS)
- `sh` - Bourne shell
- `pwsh` - PowerShell Core
- `powershell` - Windows PowerShell
- `cmd` - Windows Command Prompt
- `python` - Python interpreter

### 2. Use github.action_path

```yaml
# Reference files relative to action directory
- run: ${{ github.action_path }}/scripts/setup.sh
  shell: bash

- run: |
    cat ${{ github.action_path }}/config/default.json
  shell: bash
```

### 3. Provide Sensible Defaults

```yaml
inputs:
  node-version:
    description: 'Node.js version'
    default: '20'
  install-command:
    description: 'Install command'
    default: 'npm ci'
  working-directory:
    description: 'Working directory'
    default: '.'
```

### 4. Document Inputs and Outputs

```yaml
name: 'Setup Project'
description: |
  Install dependencies and setup project environment.
  Supports npm, yarn, and pnpm package managers.

inputs:
  node-version:
    description: |
      Node.js version to use.
      Supports: 18, 20, 22
      Default: 20
    default: '20'
  package-manager:
    description: |
      Package manager to use.
      Options: npm, yarn, pnpm
      Default: npm
    default: 'npm'

outputs:
  cache-hit:
    description: |
      Whether dependencies were restored from cache.
      Values: 'true' or 'false'
    value: ${{ steps.cache.outputs.cache-hit }}
```

### 5. Handle Errors Gracefully

```yaml
runs:
  using: "composite"
  steps:
    - name: Setup
      shell: bash
      run: ./setup.sh || true

    - name: Build
      shell: bash
      run: |
        if ! npm run build; then
          echo "::error::Build failed"
          exit 1
        fi

    - if: failure()
      shell: bash
      run: echo "::warning::Action failed, check logs"
```

### 6. Use Conditional Steps

```yaml
inputs:
  run-tests:
    description: 'Run tests'
    default: 'true'

runs:
  using: "composite"
  steps:
    - name: Build
      shell: bash
      run: npm run build

    - if: inputs.run-tests == 'true'
      name: Test
      shell: bash
      run: npm test
```

### 7. Pin Action Versions

```yaml
# ✅ Pin to commit SHA
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608  # v5.0.0

# ⚠️  Tag can be moved
- uses: actions/checkout@v5
```

---

## Common Patterns

### Pattern 1: Setup and Cache

```yaml
name: 'Setup Node.js with Cache'
description: 'Setup Node.js and cache dependencies'

inputs:
  node-version:
    description: 'Node.js version'
    default: '20'
  cache-key-prefix:
    description: 'Cache key prefix'
    default: 'deps'

outputs:
  cache-hit:
    description: "Cache hit status"
    value: ${{ steps.cache.outputs.cache-hit }}

runs:
  using: "composite"
  steps:
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}

    - id: cache
      uses: actions/cache@v4
      with:
        path: node_modules
        key: ${{ runner.os }}-${{ inputs.cache-key-prefix }}-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ runner.os }}-${{ inputs.cache-key-prefix }}-

    - if: steps.cache.outputs.cache-hit != 'true'
      shell: bash
      run: npm ci
```

### Pattern 2: Multi-Step Validation

```yaml
name: 'Validate Code Quality'
description: 'Run linting, type checking, and tests'

inputs:
  skip-tests:
    description: 'Skip test execution'
    default: 'false'

runs:
  using: "composite"
  steps:
    - name: Lint
      shell: bash
      run: npm run lint

    - name: Type Check
      shell: bash
      run: npm run type-check

    - if: inputs.skip-tests != 'true'
      name: Test
      shell: bash
      run: npm test

    - if: always()
      name: Generate Report
      shell: bash
      run: |
        echo "# Quality Report" >> $GITHUB_STEP_SUMMARY
        echo "✅ Linting passed" >> $GITHUB_STEP_SUMMARY
        echo "✅ Type checking passed" >> $GITHUB_STEP_SUMMARY
```

### Pattern 3: Conditional Tool Installation

```yaml
name: 'Install Tools'
description: 'Install required development tools'

inputs:
  install-docker:
    description: 'Install Docker'
    default: 'false'
  install-kubectl:
    description: 'Install kubectl'
    default: 'false'

runs:
  using: "composite"
  steps:
    - if: inputs.install-docker == 'true'
      name: Setup Docker
      uses: docker/setup-buildx-action@v3

    - if: inputs.install-kubectl == 'true'
      name: Install kubectl
      shell: bash
      run: |
        curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
        chmod +x kubectl
        sudo mv kubectl /usr/local/bin/
```

### Pattern 4: Build Matrix Support

```yaml
name: 'Build Project'
description: 'Build for different configurations'

inputs:
  build-type:
    description: 'Build type: development, production'
    default: 'production'
  target-platform:
    description: 'Target platform'
    default: 'linux'

runs:
  using: "composite"
  steps:
    - name: Configure build
      shell: bash
      run: |
        echo "BUILD_TYPE=${{ inputs.build-type }}" >> $GITHUB_ENV
        echo "PLATFORM=${{ inputs.target-platform }}" >> $GITHUB_ENV

    - name: Build
      shell: bash
      run: |
        npm run build -- --mode $BUILD_TYPE --platform $PLATFORM

    - name: Upload artifact
      uses: actions/upload-artifact@v4
      with:
        name: build-${{ inputs.build-type }}-${{ inputs.target-platform }}
        path: dist/
```

### Pattern 5: Notification Action

```yaml
name: 'Send Notification'
description: 'Send build status notifications'

inputs:
  webhook-url:
    description: 'Webhook URL'
    required: true
  status:
    description: 'Build status: success, failure'
    required: true
  message:
    description: 'Custom message'
    default: ''

runs:
  using: "composite"
  steps:
    - name: Prepare payload
      id: payload
      shell: bash
      run: |
        MESSAGE="${{ inputs.message }}"
        if [ -z "$MESSAGE" ]; then
          MESSAGE="Build ${{ inputs.status }} for ${{ github.repository }}"
        fi

        PAYLOAD=$(cat <<EOF
        {
          "status": "${{ inputs.status }}",
          "message": "$MESSAGE",
          "repository": "${{ github.repository }}",
          "ref": "${{ github.ref }}",
          "sha": "${{ github.sha }}"
        }
        EOF
        )

        echo "payload<<EOF" >> $GITHUB_OUTPUT
        echo "$PAYLOAD" >> $GITHUB_OUTPUT
        echo "EOF" >> $GITHUB_OUTPUT

    - name: Send notification
      shell: bash
      run: |
        curl -X POST \
          -H "Content-Type: application/json" \
          -d '${{ steps.payload.outputs.payload }}' \
          ${{ inputs.webhook-url }}
```

### Pattern 6: Cleanup Action

```yaml
name: 'Cleanup Workspace'
description: 'Clean build artifacts and caches'

inputs:
  remove-node-modules:
    description: 'Remove node_modules'
    default: 'true'
  remove-build:
    description: 'Remove build directory'
    default: 'true'
  remove-cache:
    description: 'Remove cache'
    default: 'false'

runs:
  using: "composite"
  steps:
    - if: inputs.remove-node-modules == 'true'
      shell: bash
      run: rm -rf node_modules

    - if: inputs.remove-build == 'true'
      shell: bash
      run: rm -rf dist build out

    - if: inputs.remove-cache == 'true'
      shell: bash
      run: rm -rf .cache ~/.npm ~/.yarn
```

---

## Comparison with Reusable Workflows

| Feature | Composite Actions | Reusable Workflows |
|---------|------------------|-------------------|
| **Scope** | Step-level | Job-level |
| **Trigger** | `uses:` in step | `uses:` in job |
| **Location** | `action.yml` | `.github/workflows/*.yml` |
| **Secrets** | Must pass explicitly | Inherit by default |
| **Environment Vars** | Inherit from job | Do not inherit |
| **Outputs** | Step outputs | Job outputs |
| **File Sharing** | Same workspace | Requires artifacts |
| **Nesting** | Up to 10 levels | Up to 10 levels |
| **Best For** | Utility functions | Complete CI/CD jobs |

**When to Use Composite Actions:**
- Packaging step sequences (5-20 steps)
- Setup/teardown operations
- Need access to same workspace
- Distributing via marketplace

**When to Use Reusable Workflows:**
- Standardizing entire jobs
- Multi-job orchestration
- Need job-level configuration
- Cross-repository job reuse

---

## Publishing to Marketplace

### 1. Create Public Repository

```
my-action/
├── action.yml
├── README.md
├── LICENSE
└── .github/
    └── workflows/
        └── test.yml
```

### 2. Complete action.yml Metadata

```yaml
name: 'My Awesome Action'
description: 'Does something awesome'
author: 'Your Name'
branding:
  icon: 'package'
  color: 'blue'

inputs:
  # Input definitions

outputs:
  # Output definitions

runs:
  using: "composite"
  steps:
    # Steps
```

### 3. Create README.md

```markdown
# My Awesome Action

Description of what your action does.

## Usage

\`\`\`yaml
- uses: username/my-action@v1
  with:
    input-name: value
\`\`\`

## Inputs

- `input-name` - Description

## Outputs

- `output-name` - Description

## Example

\`\`\`yaml
# Full example workflow
\`\`\`
```

### 4. Tag Release

```bash
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0

# Create major version tag
git tag -fa v1 -m "Update v1 to v1.0.0"
git push origin v1 --force
```

### 5. Publish to Marketplace

1. Go to repository on GitHub
2. Click "Releases" → "Create a new release"
3. Select tag (v1.0.0)
4. Check "Publish this Action to the GitHub Marketplace"
5. Fill in details
6. Publish release

---

## Troubleshooting

### Issue: Shell Not Specified

**Error:** `Error: Required property is missing: shell`

**Solution:**
```yaml
# Add shell to all run steps
- run: echo "Hello"
  shell: bash
```

### Issue: Cannot Access Script Files

**Error:** Script file not found

**Solution:**
```yaml
# Use github.action_path
- run: ${{ github.action_path }}/scripts/setup.sh
  shell: bash
```

### Issue: Inputs Not Working

**Problem:** Input values are empty

**Solution:**
```yaml
# Ensure inputs are defined in action.yml
inputs:
  my-input:
    description: 'Description'
    default: 'default-value'

# Access with correct syntax
- run: echo "${{ inputs.my-input }}"
  shell: bash
```

### Issue: Outputs Not Available

**Problem:** Cannot access step outputs

**Solution:**
```yaml
# Step must have id
- id: my-step
  run: echo "result=value" >> $GITHUB_OUTPUT
  shell: bash

# Output references step id
outputs:
  result:
    value: ${{ steps.my-step.outputs.result }}
```

---

For job-level reuse, see `reusable-workflows.md`.

```

### references/caching-strategies.md

```markdown
# Caching Strategies and Optimization

Techniques for optimizing GitHub Actions workflows through caching, parallelization, and resource management.

## Table of Contents

1. [Caching Overview](#caching-overview)
2. [Built-in Setup Action Caching](#built-in-setup-action-caching)
3. [Manual Caching with actions/cache](#manual-caching-with-actionscache)
4. [Cache Key Strategies](#cache-key-strategies)
5. [Docker Layer Caching](#docker-layer-caching)
6. [Parallelization Strategies](#parallelization-strategies)
7. [Workflow Optimization](#workflow-optimization)
8. [Resource Management](#resource-management)

---

## Caching Overview

### What Gets Cached

**Suitable for Caching:**
- Package manager dependencies (npm, pip, maven, etc.)
- Build outputs (compiled code, generated files)
- Downloaded tools and binaries
- Test fixtures and data
- Docker layers

**NOT Suitable for Caching:**
- Secrets or sensitive data
- Very large files (>10GB limit)
- Files that change every run
- OS-specific system files

### Cache Limits

- **Size Limit:** 10GB per repository
- **Retention:** 7 days for unused caches
- **Eviction:** Oldest caches removed when limit reached
- **Access:** Read-only from forks (can't save caches)

### Cache Scope

- **Branch:** Caches created on branch available to that branch and default branch
- **Default Branch:** Caches available to all branches
- **Pull Requests:** Can restore caches from base branch

---

## Built-in Setup Action Caching

Most setup actions include built-in caching. This is the recommended approach.

### Node.js (npm/yarn/pnpm)

```yaml
- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'  # or 'yarn', 'pnpm'
```

**What it Caches:**
- `npm`: `~/.npm`
- `yarn`: `~/.yarn/cache`
- `pnpm`: `~/.pnpm-store`

**Cache Key:** Based on lock file hash

### Python (pip/pipenv/poetry)

```yaml
- uses: actions/setup-python@v5
  with:
    python-version: '3.11'
    cache: 'pip'  # or 'pipenv', 'poetry'
```

**What it Caches:**
- `pip`: `~/.cache/pip`
- `pipenv`: `~/.cache/pipenv`
- `poetry`: `~/.cache/pypoetry`

**Cache Key:** Based on requirements.txt or lock file

### Java (Maven/Gradle)

```yaml
- uses: actions/setup-java@v4
  with:
    java-version: '17'
    distribution: 'temurin'
    cache: 'maven'  # or 'gradle'
```

**What it Caches:**
- `maven`: `~/.m2/repository`
- `gradle`: `~/.gradle/caches`, `~/.gradle/wrapper`

### .NET

```yaml
- uses: actions/setup-dotnet@v4
  with:
    dotnet-version: '6.x'
    cache: true
```

**What it Caches:** NuGet packages

### Go

```yaml
- uses: actions/setup-go@v5
  with:
    go-version: '1.21'
    cache: true
```

**What it Caches:** Go modules and build cache

### Ruby (Bundler)

```yaml
- uses: ruby/setup-ruby@v1
  with:
    ruby-version: '3.2'
    bundler-cache: true
```

---

## Manual Caching with actions/cache

Use `actions/cache@v4` for custom caching needs.

### Basic Usage

```yaml
- name: Cache dependencies
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-npm-
```

**Parameters:**
- `path`: Directory or file(s) to cache (required)
- `key`: Unique cache identifier (required)
- `restore-keys`: Fallback keys for partial matches (optional)
- `upload-chunk-size`: Upload chunk size in bytes (optional, default: 32MB)

### Multiple Paths

```yaml
- uses: actions/cache@v4
  with:
    path: |
      ~/.npm
      ~/.cache
      node_modules
    key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}
```

### Checking Cache Hit

```yaml
- name: Cache dependencies
  id: cache-deps
  uses: actions/cache@v4
  with:
    path: node_modules
    key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}

- name: Install dependencies
  if: steps.cache-deps.outputs.cache-hit != 'true'
  run: npm ci

- name: Use cached dependencies
  if: steps.cache-deps.outputs.cache-hit == 'true'
  run: echo "Using cached dependencies"
```

### Save Cache Only on Success

```yaml
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
    save-always: false  # Default: saves on success only
```

### Cache Read-Only Mode

```yaml
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
    lookup-only: true  # Don't save, only restore
```

---

## Cache Key Strategies

### Hash-Based Keys

**Dependency Files:**

```yaml
# Single lock file
key: ${{ runner.os }}-deps-${{ hashFiles('package-lock.json') }}

# Multiple lock files
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}

# Multiple file types
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}
```

**Source Files (Incremental Builds):**

```yaml
key: ${{ runner.os }}-build-${{ hashFiles('src/**/*.ts') }}
```

### Composite Keys

```yaml
# OS + Node version + Dependencies
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}

# Branch + Dependencies
key: ${{ runner.os }}-${{ github.ref_name }}-${{ hashFiles('**/package-lock.json') }}

# Date-based (weekly cache rotation)
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}-${{ github.run_number }}
```

### Restore Keys (Fallback)

```yaml
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
  ${{ runner.os }}-deps-
  ${{ runner.os }}-
```

**Matching Logic:**
1. Exact match on `key`
2. Prefix match on `restore-keys` (most recent)
3. No cache if no match

**Example:**
- Key: `Linux-deps-abc123`
- Restore keys: `Linux-deps-`, `Linux-`
- Will match: `Linux-deps-xyz789` (if `abc123` doesn't exist)

### Dynamic Keys

```yaml
# From file content
- id: get-date
  run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT

- uses: actions/cache@v4
  with:
    path: ~/.cache
    key: ${{ runner.os }}-cache-${{ steps.get-date.outputs.date }}
```

---

## Docker Layer Caching

### Using docker/build-push-action

```yaml
- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Build and push
  uses: docker/build-push-action@v5
  with:
    context: .
    push: false
    cache-from: type=gha
    cache-to: type=gha,mode=max
```

**Cache Backends:**
- `type=gha` - GitHub Actions cache
- `type=registry` - Container registry
- `type=local` - Local directory
- `type=s3` - S3 bucket

### Cache Mode

```yaml
# Default mode (minimal layers cached)
cache-to: type=gha

# Max mode (all layers cached)
cache-to: type=gha,mode=max
```

### Multi-Platform Builds with Cache

```yaml
- uses: docker/build-push-action@v5
  with:
    platforms: linux/amd64,linux/arm64
    cache-from: type=gha
    cache-to: type=gha,mode=max
    build-args: |
      BUILDKIT_INLINE_CACHE=1
```

### Registry Cache

```yaml
- name: Build and push
  uses: docker/build-push-action@v5
  with:
    push: true
    tags: user/app:latest
    cache-from: type=registry,ref=user/app:buildcache
    cache-to: type=registry,ref=user/app:buildcache,mode=max
```

---

## Parallelization Strategies

### Independent Parallel Jobs

```yaml
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: npm run lint

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

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: npm run build

  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: npm audit
```

**Result:** All 4 jobs run simultaneously

### Matrix Strategy

```yaml
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      max-parallel: 4
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: [18, 20, 22]
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm test
```

**Result:** 9 jobs (3 OS × 3 Node versions)

### Test Splitting

```yaml
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v5
      - run: npm test -- --shard=${{ matrix.shard }}/4
```

### Monorepo Parallel Builds

```yaml
jobs:
  changes:
    runs-on: ubuntu-latest
    outputs:
      frontend: ${{ steps.filter.outputs.frontend }}
      backend: ${{ steps.filter.outputs.backend }}
    steps:
      - uses: actions/checkout@v5
      - uses: dorny/paths-filter@v2
        id: filter
        with:
          filters: |
            frontend:
              - 'packages/frontend/**'
            backend:
              - 'packages/backend/**'

  frontend:
    needs: changes
    if: needs.changes.outputs.frontend == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: npm run build --workspace=frontend

  backend:
    needs: changes
    if: needs.changes.outputs.backend == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: npm run build --workspace=backend
```

---

## Workflow Optimization

### Minimize Checkout

**Shallow Clone:**

```yaml
- uses: actions/checkout@v5
  with:
    fetch-depth: 1
```

**Sparse Checkout:**

```yaml
- uses: actions/checkout@v5
  with:
    sparse-checkout: |
      src/
      package.json
      package-lock.json
```

**Partial Clone:**

```yaml
- uses: actions/checkout@v5
  with:
    fetch-depth: 0
    filter: blob:none
```

### Conditional Steps

```yaml
# Skip on specific branches
- name: Deploy
  if: github.ref == 'refs/heads/main'
  run: ./deploy.sh

# Skip on PR
- name: Publish
  if: github.event_name != 'pull_request'
  run: npm publish

# Run only on schedule
- name: Cleanup
  if: github.event_name == 'schedule'
  run: ./cleanup.sh

# Skip if files unchanged
- name: Build frontend
  if: contains(github.event.head_commit.modified, 'frontend/')
  run: npm run build:frontend
```

### Early Termination

```yaml
- name: Check commit message
  run: |
    if [[ "${{ github.event.head_commit.message }}" =~ \[skip\ ci\] ]]; then
      echo "Skipping CI"
      exit 78  # Neutral exit code
    fi
```

### Artifacts Strategy

**Minimize Retention:**

```yaml
- uses: actions/upload-artifact@v4
  with:
    name: build
    path: dist/
    retention-days: 1  # Delete after 1 day
```

**Compress Before Upload:**

```yaml
- name: Compress artifacts
  run: tar -czf dist.tar.gz dist/

- uses: actions/upload-artifact@v4
  with:
    name: build
    path: dist.tar.gz
```

### Concurrency Control

**Cancel Redundant Runs:**

```yaml
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
```

**Per-Job Concurrency:**

```yaml
jobs:
  deploy:
    concurrency:
      group: deploy-production
      cancel-in-progress: false
    steps: [...]
```

---

## Resource Management

### Self-Hosted Runner Optimization

**Clean Workspace:**

```yaml
jobs:
  build:
    runs-on: self-hosted
    steps:
      - name: Clean workspace
        run: |
          rm -rf ${{ github.workspace }}/*
          rm -rf ${{ github.workspace }}/.??*

      - uses: actions/checkout@v5
```

**Pre-installed Tools:**

```yaml
# Verify tools available
- name: Check tools
  run: |
    node --version
    npm --version
    docker --version
```

### Memory and CPU Constraints

**Container Resources:**

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    container:
      image: node:20
      options: --cpus 2 --memory 4g
    steps: [...]
```

**Job Timeout:**

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps: [...]
```

**Step Timeout:**

```yaml
- name: Long running task
  run: ./slow-process.sh
  timeout-minutes: 15
```

### Workflow Limits

**GitHub-Hosted Runners (Free Tier):**
- **Linux:** 2-core CPU, 7GB RAM, 14GB SSD
- **Windows:** 2-core CPU, 7GB RAM, 14GB SSD
- **macOS:** 3-core CPU, 14GB RAM, 14GB SSD
- **Concurrent Jobs:** 20 (free), 60 (Team), 180 (Enterprise)

**Usage Limits:**
- **Public repos:** Unlimited minutes
- **Private repos:** 2,000 min/month (free), 3,000 (Team)
- **Workflow file size:** 20KB per file, 100 files per repo
- **Workflow run time:** 72 hours maximum
- **API requests:** 1,000 per hour per repo

---

## Advanced Patterns

### Multi-Layer Caching

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # Layer 1: Dependencies
      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: ${{ runner.os }}-npm-

      # Layer 2: Node modules
      - uses: actions/cache@v4
        with:
          path: node_modules
          key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }}

      # Layer 3: Build cache
      - uses: actions/cache@v4
        with:
          path: .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.tsx') }}
          restore-keys: |
            ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
            ${{ runner.os }}-nextjs-

      - run: npm ci
      - run: npm run build
```

### Incremental Build Cache

```yaml
- name: Cache build
  uses: actions/cache@v4
  with:
    path: |
      dist/
      .cache/
    key: build-${{ hashFiles('src/**') }}-${{ github.sha }}
    restore-keys: |
      build-${{ hashFiles('src/**') }}-
      build-

- name: Incremental build
  run: npm run build -- --incremental
```

### Cross-Job Caching

```yaml
jobs:
  setup:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/cache@v4
        id: cache
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
      - if: steps.cache.outputs.cache-hit != 'true'
        run: npm ci

  build:
    needs: setup
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
      - run: npm ci --prefer-offline
      - run: npm run build
```

---

## Monitoring and Debugging

### Cache Statistics

View cache usage in repository:
- Settings → Actions → Caches
- See size, creation date, last accessed
- Manually delete caches if needed

### Debugging Cache Issues

**Enable Debug Logging:**

Add repository secret: `ACTIONS_STEP_DEBUG=true`

**Check Cache Hits:**

```yaml
- name: Cache dependencies
  id: cache
  uses: actions/cache@v4
  with:
    path: node_modules
    key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}

- name: Debug cache
  run: |
    echo "Cache hit: ${{ steps.cache.outputs.cache-hit }}"
    echo "Cache key: ${{ steps.cache.outputs.cache-primary-key }}"
```

**List Cache Contents:**

```yaml
- name: List cached files
  run: |
    echo "=== Cached Dependencies ==="
    ls -lah node_modules/ || echo "No node_modules cached"
```

---

For security optimization, see `security-practices.md`.

```

### examples/matrix-build.yml

```yaml
# Matrix Build Example
# Demonstrates: Cross-platform and multi-version testing

name: Matrix Build

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

jobs:
  test:
    name: Test on ${{ matrix.os }} with Node ${{ matrix.node }}
    runs-on: ${{ matrix.os }}
    strategy:
      # Don't cancel all jobs if one fails (see all results)
      fail-fast: false
      # Limit concurrent jobs
      max-parallel: 6
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: [18, 20, 22]
        # Add specific configuration
        include:
          - os: ubuntu-latest
            node: 20
            experimental: true
            coverage: true
        # Exclude problematic combinations
        exclude:
          - os: windows-latest
            node: 18

    steps:
      - name: Checkout code
        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608  # v5.0.0

      - name: Setup Node.js ${{ matrix.node }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test
        continue-on-error: ${{ matrix.experimental || false }}

      - name: Generate coverage
        if: matrix.coverage
        run: npm test -- --coverage

      - name: Upload coverage
        if: matrix.coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage-${{ matrix.os }}-node${{ matrix.node }}
          path: coverage/

  build-matrix:
    name: Build for ${{ matrix.platform }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        platform: [linux, windows, darwin]
        arch: [amd64, arm64]
        exclude:
          - platform: windows
            arch: arm64
    steps:
      - name: Checkout code
        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608  # v5.0.0

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.21'
          cache: true

      - name: Build
        env:
          GOOS: ${{ matrix.platform }}
          GOARCH: ${{ matrix.arch }}
        run: go build -o bin/app-${{ matrix.platform }}-${{ matrix.arch }}

      - name: Upload binary
        uses: actions/upload-artifact@v4
        with:
          name: app-${{ matrix.platform }}-${{ matrix.arch }}
          path: bin/app-${{ matrix.platform }}-${{ matrix.arch }}

```

### references/security-practices.md

```markdown
# Security Best Practices for GitHub Actions

Comprehensive security guide for GitHub Actions workflows, including secrets management, OIDC authentication, permissions, and vulnerability prevention.

## Table of Contents

1. [Secrets Management](#secrets-management)
2. [OIDC Authentication](#oidc-authentication)
3. [Permissions and Token Scope](#permissions-and-token-scope)
4. [Action Pinning and Supply Chain Security](#action-pinning-and-supply-chain-security)
5. [Pull Request Security](#pull-request-security)
6. [Environment Protection](#environment-protection)
7. [Script Injection Prevention](#script-injection-prevention)
8. [Security Scanning](#security-scanning)

---

## Secrets Management

### Using GitHub Secrets

**Repository Secrets:**

Settings → Secrets and variables → Actions → New repository secret

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: ./deploy.sh
```

**Environment Secrets:**

Settings → Environments → [environment] → Add secret

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production  # Uses production-specific secrets
    steps:
      - env:
          API_KEY: ${{ secrets.API_KEY }}  # Environment secret overrides repository secret
        run: ./deploy.sh
```

**Organization Secrets:**

Organization settings → Secrets and variables → Actions

Available to all repos in organization (or selected repos).

### Secret Handling Best Practices

**❌ Never Log Secrets:**

```yaml
# BAD - exposes secret in logs
- run: echo "API_KEY=${{ secrets.API_KEY }}"

# BAD - can leak via error messages
- run: curl -H "Authorization: Bearer ${{ secrets.API_KEY }}" https://api.example.com
```

**✅ Safe Secret Usage:**

```yaml
# GOOD - secret not in command output
- env:
    API_KEY: ${{ secrets.API_KEY }}
  run: ./deploy.sh

# GOOD - mask sensitive values
- run: |
    echo "::add-mask::${{ secrets.API_KEY }}"
    # Now safe to reference
```

**Never Commit Secrets:**

`.gitignore`:
```
.env
.env.local
.env.*.local
secrets.yml
credentials.json
*.key
*.pem
```

**Secret Scanning:**

Enable in Settings → Code security and analysis:
- Secret scanning
- Push protection (blocks commits with secrets)

---

## OIDC Authentication

OIDC (OpenID Connect) enables federated identity for cloud providers without storing long-lived credentials.

### AWS OIDC

**Setup (AWS Side):**

1. Create OIDC provider in IAM:
   - Provider URL: `https://token.actions.githubusercontent.com`
   - Audience: `sts.amazonaws.com`

2. Create IAM role with trust policy:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:OWNER/REPO:*"
        }
      }
    }
  ]
}
```

**Workflow:**

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # Required for OIDC
      contents: read
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
          aws-region: us-east-1

      - run: |
          aws s3 sync ./dist s3://my-bucket
          aws cloudfront create-invalidation --distribution-id $DIST_ID --paths "/*"
```

### Azure OIDC

**Setup (Azure Side):**

1. Create App Registration
2. Create Federated Credential:
   - Subject identifier: `repo:OWNER/REPO:ref:refs/heads/main`

**Workflow:**

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - run: az webapp deploy --resource-group $RG --name $APP_NAME --src-path ./dist
```

### Google Cloud OIDC

**Setup (GCP Side):**

1. Create Workload Identity Pool
2. Create Workload Identity Provider
3. Grant service account access

**Workflow:**

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: 'projects/PROJECT_ID/locations/global/workloadIdentityPools/POOL/providers/PROVIDER'
          service_account: 'SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com'

      - run: gcloud app deploy
```

### OIDC Benefits

**Security:**
- No long-lived credentials stored as secrets
- Automatic credential rotation
- Short-lived tokens (1 hour default)
- Fine-grained access control

**Compliance:**
- Audit trail via cloud provider logs
- No credential exposure risk
- Meets security compliance requirements

---

## Permissions and Token Scope

### GITHUB_TOKEN Permissions

**Default (Permissive - Legacy):**

```yaml
permissions: write-all
```

**Recommended (Least Privilege):**

```yaml
permissions:
  contents: read
  pull-requests: write
```

**Available Permissions:**

| Permission | Read | Write | Description |
|------------|------|-------|-------------|
| `actions` | ✓ | ✓ | GitHub Actions |
| `checks` | ✓ | ✓ | Check runs and suites |
| `contents` | ✓ | ✓ | Repository contents |
| `deployments` | ✓ | ✓ | Deployments |
| `id-token` | - | ✓ | OIDC token (write only) |
| `issues` | ✓ | ✓ | Issues and comments |
| `packages` | ✓ | ✓ | GitHub Packages |
| `pull-requests` | ✓ | ✓ | Pull requests |
| `repository-projects` | ✓ | ✓ | Projects (classic) |
| `security-events` | ✓ | ✓ | Security events |
| `statuses` | ✓ | ✓ | Commit statuses |

### Workflow-Level Permissions

```yaml
name: CI

permissions:
  contents: read
  pull-requests: write

jobs:
  test:
    runs-on: ubuntu-latest
    steps: [...]
```

### Job-Level Permissions

```yaml
jobs:
  test:
    permissions:
      contents: read
    runs-on: ubuntu-latest
    steps: [...]

  deploy:
    permissions:
      contents: write
      deployments: write
    runs-on: ubuntu-latest
    steps: [...]
```

### Disable Permissions

```yaml
permissions: {}  # No permissions
```

---

## Action Pinning and Supply Chain Security

### Pin Actions to Commit SHAs

**❌ Bad (Tags can be moved):**

```yaml
- uses: actions/checkout@v5
- uses: some-org/[email protected]
```

**✅ Good (SHA is immutable):**

```yaml
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608  # v5.0.0
- uses: some-org/action@a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0  # v1.2.3
```

**Benefits:**
- Immutable reference (cannot be modified)
- Protection against tag hijacking
- Specific version for reproducibility

### Get Commit SHA for Tag

```bash
# Find SHA for a tag
git ls-remote --tags https://github.com/actions/checkout refs/tags/v5

# Output: abc123...  refs/tags/v5
```

### Dependabot for Actions

**File:** `.github/dependabot.yml`

```yaml
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    labels:
      - "dependencies"
      - "github-actions"
    reviewers:
      - "security-team"
    commit-message:
      prefix: "chore"
      include: "scope"
```

**Benefits:**
- Automated updates for pinned actions
- Creates PRs with SHA updates
- Security vulnerability notifications

### Verify Action Source

**Before Using Third-Party Actions:**

1. **Check Repository:**
   - Stars (>1,000 = widely trusted)
   - Activity (recent commits, maintained)
   - Issues (security concerns, responsiveness)

2. **Review Code:**
   - Read action source code
   - Look for suspicious behavior
   - Check for security advisories

3. **Verify Publisher:**
   - Official organization (GitHub, AWS, Google, etc.)
   - Verified publisher badge
   - Known maintainer

4. **Use Marketplace:**
   - Verified creators
   - Usage statistics
   - Community feedback

---

## Pull Request Security

### pull_request vs pull_request_target

**pull_request (Safe):**

```yaml
on:
  pull_request:
    branches: [main]

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

**Behavior:**
- Runs workflow from PR head (fork)
- No access to secrets
- Safe for untrusted code
- Cannot write to repository

**pull_request_target (Dangerous):**

```yaml
on:
  pull_request_target:
    branches: [main]

jobs:
  comment:
    if: github.event.pull_request.head.repo.full_name == github.repository
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: 'Thanks for the PR!'
            })
```

**Behavior:**
- Runs workflow from base branch (main)
- Has access to secrets
- Can write to repository
- Dangerous with untrusted code

**Security Rule:**

```yaml
# ❌ NEVER do this with pull_request_target
on: pull_request_target
jobs:
  test:
    steps:
      - uses: actions/checkout@v5  # Checks out untrusted PR code
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - run: npm test  # Runs untrusted code with secrets access

# ✅ SAFE: Use pull_request instead
on: pull_request
jobs:
  test:
    steps:
      - uses: actions/checkout@v5
      - run: npm test
```

### Fork PR Permissions

**Repository Settings:**

Settings → Actions → General → Fork pull request workflows

Options:
- **Require approval for first-time contributors** (Recommended)
- **Require approval for all outside collaborators**
- **Run workflows from fork pull requests**

---

## Environment Protection

### Environment Configuration

Settings → Environments → [environment name]

**Protection Rules:**
- **Required reviewers:** 1-6 reviewers must approve
- **Wait timer:** Delay before deployment (0-43,200 minutes)
- **Branch restrictions:** Only specific branches can deploy

**Example:**

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://prod.example.com
    steps:
      - run: ./deploy.sh
```

**Benefits:**
- Manual approval gate
- Environment-specific secrets
- Deployment history
- URL tracking

### Deployment Environments

```yaml
jobs:
  deploy-staging:
    environment: staging
    steps:
      - run: ./deploy.sh staging

  deploy-production:
    needs: deploy-staging
    environment: production  # Requires approval
    steps:
      - run: ./deploy.sh production
```

---

## Script Injection Prevention

### Unsafe: Direct Variable Interpolation

**❌ Vulnerable to injection:**

```yaml
- name: Print commit message
  run: echo "${{ github.event.head_commit.message }}"
```

**Attack:** Commit message like `"; rm -rf / #"` could execute arbitrary commands.

### Safe: Use Environment Variables

**✅ Safe approach:**

```yaml
- name: Print commit message
  env:
    COMMIT_MSG: ${{ github.event.head_commit.message }}
  run: echo "$COMMIT_MSG"
```

### Safe: Use Intermediate Steps

**✅ Sanitize input:**

```yaml
- name: Validate input
  env:
    USER_INPUT: ${{ github.event.inputs.version }}
  run: |
    if [[ ! "$USER_INPUT" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
      echo "Invalid version format"
      exit 1
    fi
    echo "VERSION=$USER_INPUT" >> $GITHUB_ENV

- name: Use validated input
  run: echo "Deploying version $VERSION"
```

### Safe: Use Actions for Complex Operations

Instead of inline scripts with user input:

```yaml
# ✅ Use actions/github-script for safe GitHub API calls
- uses: actions/github-script@v7
  with:
    script: |
      const title = context.payload.pull_request.title;
      await github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: `PR Title: ${title}`
      });
```

---

## Security Scanning

### CodeQL (SAST)

```yaml
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'  # Weekly on Monday

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      contents: read
    strategy:
      matrix:
        language: ['javascript', 'python']
    steps:
      - uses: actions/checkout@v5

      - uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}

      - uses: github/codeql-action/autobuild@v3

      - uses: github/codeql-action/analyze@v3
```

### Dependency Scanning

**Dependabot (Built-in):**

Settings → Code security → Dependabot

**npm audit:**

```yaml
- name: Security audit
  run: npm audit --audit-level=high
```

**OWASP Dependency Check:**

```yaml
- name: OWASP Dependency Check
  uses: dependency-check/Dependency-Check_Action@main
  with:
    project: 'my-project'
    path: '.'
    format: 'HTML'
```

### Container Scanning

**Trivy:**

```yaml
- name: Build image
  run: docker build -t myimage:${{ github.sha }} .

- name: Scan image
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: myimage:${{ github.sha }}
    format: 'sarif'
    output: 'trivy-results.sarif'

- name: Upload results
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: 'trivy-results.sarif'
```

### Secret Scanning

**gitleaks:**

```yaml
- name: Scan for secrets
  uses: gitleaks/gitleaks-action@v2
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

---

## Security Checklist

### Workflow Security

- [ ] Pin actions to commit SHAs
- [ ] Use minimal GITHUB_TOKEN permissions
- [ ] Enable Dependabot for action updates
- [ ] Review third-party actions before use
- [ ] Use environment variables for secrets
- [ ] Never log secrets
- [ ] Enable secret scanning
- [ ] Enable push protection

### Authentication

- [ ] Use OIDC for cloud providers (no long-lived credentials)
- [ ] Rotate secrets regularly
- [ ] Use environment-specific secrets
- [ ] Implement environment protection rules

### Pull Requests

- [ ] Use `pull_request` for untrusted code
- [ ] Restrict `pull_request_target` usage
- [ ] Require approval for fork PRs
- [ ] Never checkout untrusted code with secrets

### Deployment

- [ ] Use environment protection for production
- [ ] Require manual approval
- [ ] Implement deployment gates
- [ ] Use separate environments (dev, staging, prod)

### Monitoring

- [ ] Enable security scanning (CodeQL, Dependabot)
- [ ] Monitor workflow logs
- [ ] Review security advisories
- [ ] Audit GITHUB_TOKEN usage

---

For optimization techniques, see `caching-strategies.md`.

```

### references/workflow-syntax.md

```markdown
# GitHub Actions Workflow Syntax Reference

Complete reference for GitHub Actions YAML syntax, structure, and configuration options.

## Table of Contents

1. [Workflow File Structure](#workflow-file-structure)
2. [Trigger Configuration (on)](#trigger-configuration-on)
3. [Environment Variables](#environment-variables)
4. [Jobs Configuration](#jobs-configuration)
5. [Steps Configuration](#steps-configuration)
6. [Expressions and Contexts](#expressions-and-contexts)
7. [Filters and Patterns](#filters-and-patterns)

---

## Workflow File Structure

**Location:** `.github/workflows/*.yml` or `.github/workflows/*.yaml`

**Top-Level Keys:**

```yaml
name: Workflow Name                 # Display name (optional)
run-name: Custom run name           # Dynamic run name (optional)
on: [push, pull_request]           # Trigger events (required)
permissions: read-all              # Default permissions (optional)
env:                               # Workflow-level environment variables
  NODE_ENV: production
defaults:                          # Default settings for all jobs
  run:
    shell: bash
    working-directory: ./src
concurrency:                       # Concurrency control
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
jobs:                              # Job definitions (required)
  job_id:
    # Job configuration
```

---

## Trigger Configuration (on)

### Single Event

```yaml
on: push
```

### Multiple Events

```yaml
on: [push, pull_request, workflow_dispatch]
```

### Event with Configuration

**Push Event:**

```yaml
on:
  push:
    branches:
      - main
      - 'releases/**'
    branches-ignore:
      - 'experimental/**'
    tags:
      - v*.*.*
    paths:
      - 'src/**'
      - '**.js'
    paths-ignore:
      - 'docs/**'
      - '**.md'
```

**Pull Request Event:**

```yaml
on:
  pull_request:
    types:
      - opened
      - synchronize
      - reopened
    branches:
      - main
    paths:
      - 'src/**'
```

**Available Types:** `opened`, `synchronize`, `reopened`, `closed`, `assigned`, `unassigned`, `labeled`, `unlabeled`, `review_requested`, `ready_for_review`

**Pull Request Target (Safe for Forks):**

```yaml
on:
  pull_request_target:
    types: [opened, synchronize]
```

**Schedule Event:**

```yaml
on:
  schedule:
    - cron: '30 5 * * 1-5'  # 5:30 AM UTC, Mon-Fri
    - cron: '0 0 * * 0'     # Midnight UTC, Sunday
```

**Manual Trigger (workflow_dispatch):**

```yaml
on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy'
        required: true
        type: choice
        options:
          - dev
          - staging
          - production
        default: dev
      version:
        description: 'Version to deploy'
        required: true
        type: string
      enable-debug:
        description: 'Enable debug mode'
        required: false
        type: boolean
        default: false
```

**Input Types:** `string`, `choice`, `boolean`, `environment`

**Reusable Workflow (workflow_call):**

```yaml
on:
  workflow_call:
    inputs:
      config-path:
        required: true
        type: string
      node-version:
        required: false
        type: string
        default: '20'
    secrets:
      api-token:
        required: true
      npm-token:
        required: false
    outputs:
      build-artifact:
        description: "Name of build artifact"
        value: ${{ jobs.build.outputs.artifact-name }}
```

**Repository Dispatch:**

```yaml
on:
  repository_dispatch:
    types: [deploy, test-all]
```

**Other Events:**

```yaml
on:
  release:
    types: [published, created, edited]
  issues:
    types: [opened, labeled]
  issue_comment:
    types: [created, edited]
  deployment:
  deployment_status:
  watch:
    types: [started]  # Repository starred
```

---

## Environment Variables

### Workflow-Level

```yaml
env:
  NODE_ENV: production
  API_URL: https://api.example.com

jobs:
  build:
    steps:
      - run: echo $NODE_ENV  # Available to all jobs
```

### Job-Level

```yaml
jobs:
  build:
    env:
      BUILD_TYPE: release
    steps:
      - run: echo $BUILD_TYPE  # Available to all steps in job
```

### Step-Level

```yaml
steps:
  - name: Deploy
    env:
      API_KEY: ${{ secrets.API_KEY }}
      ENVIRONMENT: production
    run: ./deploy.sh
```

### Default Environment Variables

GitHub provides default variables:

- `GITHUB_TOKEN` - Authentication token
- `GITHUB_REPOSITORY` - Repository name (owner/repo)
- `GITHUB_REF` - Branch or tag ref
- `GITHUB_SHA` - Commit SHA
- `GITHUB_ACTOR` - Username that triggered workflow
- `GITHUB_WORKFLOW` - Workflow name
- `GITHUB_RUN_ID` - Unique run ID
- `RUNNER_OS` - Runner OS (Linux, Windows, macOS)
- `RUNNER_TEMP` - Temporary directory path

---

## Jobs Configuration

### Basic Job

```yaml
jobs:
  build:
    name: Build Application
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: npm run build
```

### Job with Dependencies

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps: [...]

  test:
    needs: build
    runs-on: ubuntu-latest
    steps: [...]

  deploy:
    needs: [build, test]
    runs-on: ubuntu-latest
    steps: [...]
```

### Job with Outputs

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.get-version.outputs.version }}
      artifact-name: build-${{ steps.get-version.outputs.version }}
    steps:
      - id: get-version
        run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
```

**Accessing Outputs:**

```yaml
jobs:
  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: echo "Version: ${{ needs.build.outputs.version }}"
```

### Job with Matrix Strategy

```yaml
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      max-parallel: 4
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: [18, 20, 22]
        include:
          - os: ubuntu-latest
            node: 20
            experimental: true
        exclude:
          - os: windows-latest
            node: 18
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
```

### Job with Environment

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://prod.example.com
    steps:
      - run: ./deploy.sh
```

**Environment Features:**
- Protection rules (required reviewers)
- Environment-specific secrets
- Deployment history
- Wait timers

### Job with Container

```yaml
jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: node:20-alpine
      env:
        NODE_ENV: test
      volumes:
        - /data:/data
      options: --cpus 2 --memory 4g
    steps:
      - run: node --version
```

### Job with Services

```yaml
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
    steps:
      - run: psql --host localhost --port 5432
```

### Job Concurrency

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    concurrency:
      group: production-deploy
      cancel-in-progress: false
    steps: [...]
```

### Job Permissions

```yaml
jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
      issues: write
    steps: [...]
```

**Available Permissions:**
- `actions` - GitHub Actions
- `checks` - Checks on code
- `contents` - Repository contents
- `deployments` - Deployments
- `id-token` - OIDC token
- `issues` - Issues and comments
- `packages` - GitHub Packages
- `pull-requests` - Pull requests
- `repository-projects` - Projects
- `security-events` - Security events
- `statuses` - Commit statuses

**Values:** `read`, `write`, `none`

### Job Conditions

```yaml
jobs:
  deploy:
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps: [...]
```

---

## Steps Configuration

### Using Actions

```yaml
- name: Checkout repository
  uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608  # v5.0.0
  with:
    fetch-depth: 0
    submodules: true
```

### Running Commands

```yaml
- name: Build application
  run: |
    npm ci
    npm run build
    npm test
```

### Running Scripts

```yaml
- name: Run script
  run: ./scripts/deploy.sh
  shell: bash
```

**Available Shells:** `bash`, `pwsh`, `python`, `sh`, `cmd`, `powershell`

### Step with ID (for outputs)

```yaml
- id: get-version
  run: |
    VERSION=$(cat VERSION)
    echo "version=$VERSION" >> $GITHUB_OUTPUT
    echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
```

**Accessing Outputs:**

```yaml
- run: echo "Version: ${{ steps.get-version.outputs.version }}"
```

### Step with Timeout

```yaml
- name: Long running task
  run: ./slow-script.sh
  timeout-minutes: 30
```

### Step with Continue on Error

```yaml
- name: Optional check
  run: npm audit
  continue-on-error: true
```

### Step Conditions

```yaml
- name: Deploy to production
  if: github.ref == 'refs/heads/main'
  run: ./deploy.sh

- name: Install dependencies
  if: steps.cache.outputs.cache-hit != 'true'
  run: npm ci

- name: Upload artifacts on failure
  if: failure()
  uses: actions/upload-artifact@v4
  with:
    name: logs
    path: logs/
```

**Status Check Functions:**
- `success()` - Previous steps succeeded
- `always()` - Always run (even on cancellation)
- `cancelled()` - Workflow was cancelled
- `failure()` - Previous step failed

---

## Expressions and Contexts

### Expression Syntax

```yaml
${{ expression }}
```

### Context Objects

**github context:**

```yaml
github.actor              # User who triggered
github.event_name         # Event type (push, pull_request, etc.)
github.ref               # Branch or tag ref
github.ref_name          # Branch or tag name (without refs/)
github.sha               # Commit SHA
github.repository        # owner/repo
github.repository_owner  # Repository owner
github.workflow          # Workflow name
github.run_id            # Unique run ID
github.run_number        # Run number
github.job               # Job ID
```

**env context:**

```yaml
env.NODE_ENV
env.API_KEY
```

**secrets context:**

```yaml
secrets.API_TOKEN
secrets.NPM_TOKEN
```

**inputs context (workflow_dispatch or workflow_call):**

```yaml
inputs.environment
inputs.version
inputs.enable-debug
```

**matrix context:**

```yaml
matrix.os
matrix.node
matrix.experimental
```

**steps context:**

```yaml
steps.build.outputs.version
steps.build.outcome        # success, failure, cancelled, skipped
steps.build.conclusion     # success, failure, cancelled, skipped, neutral
```

**needs context:**

```yaml
needs.build.outputs.version
needs.build.result         # success, failure, cancelled, skipped
```

**runner context:**

```yaml
runner.os                 # Linux, Windows, macOS
runner.arch              # X86, X64, ARM, ARM64
runner.name              # Runner name
runner.temp              # Temp directory path
runner.tool_cache        # Tool cache directory
```

**job context:**

```yaml
job.status               # success, failure, cancelled
job.services             # Service containers
```

### Operators

**Comparison:**
- `==` - Equal
- `!=` - Not equal
- `<` - Less than
- `<=` - Less than or equal
- `>` - Greater than
- `>=` - Greater than or equal

**Logical:**
- `&&` - AND
- `||` - OR
- `!` - NOT

**Example:**

```yaml
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
```

### Functions

**contains(search, item):**

```yaml
if: contains(github.event.head_commit.message, '[skip ci]')
```

**startsWith(search, prefix):**

```yaml
if: startsWith(github.ref, 'refs/tags/')
```

**endsWith(search, suffix):**

```yaml
if: endsWith(github.ref, '-beta')
```

**format(template, args):**

```yaml
run: echo ${{ format('Hello {0}', github.actor) }}
```

**join(array, separator):**

```yaml
run: echo ${{ join(github.event.commits.*.message, ', ') }}
```

**toJSON(value):**

```yaml
run: echo '${{ toJSON(github) }}'
```

**fromJSON(value):**

```yaml
strategy:
  matrix:
    version: ${{ fromJSON('[18, 20, 22]') }}
```

**hashFiles(pattern):**

```yaml
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}
```

---

## Filters and Patterns

### Branch Filters

```yaml
on:
  push:
    branches:
      - main
      - 'releases/**'      # Matches releases/v1, releases/v1/beta
      - '!releases/alpha'  # Exclude pattern
```

### Tag Filters

```yaml
on:
  push:
    tags:
      - v*.*.*            # Matches v1.0.0, v2.1.3
      - 'beta-*'          # Matches beta-1, beta-2
```

### Path Filters

```yaml
on:
  push:
    paths:
      - 'src/**'          # Any file in src/ and subdirectories
      - '**.js'           # Any .js file
      - 'config/*.json'   # JSON files in config/ (not subdirectories)
    paths-ignore:
      - 'docs/**'
      - '**.md'
      - '.github/**'
```

**Patterns:**
- `*` - Matches zero or more characters (except `/`)
- `**` - Matches zero or more directories
- `?` - Matches single character
- `!` - Negates pattern (exclude)

### Activity Type Filters

```yaml
on:
  pull_request:
    types:
      - opened
      - synchronize
      - reopened
      - closed

  issues:
    types:
      - opened
      - labeled
      - assigned
```

---

## Advanced Features

### Reusing Workflows

**Caller Workflow:**

```yaml
jobs:
  call-workflow:
    uses: octo-org/repo/.github/workflows/reusable.yml@v1
    with:
      input1: value1
    secrets:
      token: ${{ secrets.TOKEN }}
```

**Reusable Workflow:**

```yaml
on:
  workflow_call:
    inputs:
      input1:
        required: true
        type: string
    secrets:
      token:
        required: true
```

### Composite Actions

**action.yml:**

```yaml
name: 'Setup Project'
description: 'Setup project environment'
inputs:
  node-version:
    description: 'Node version'
    required: false
    default: '20'
runs:
  using: "composite"
  steps:
    - run: echo "Setting up Node ${{ inputs.node-version }}"
      shell: bash
```

### Workflow Commands

**Set output:**

```bash
echo "name=value" >> $GITHUB_OUTPUT
```

**Set environment variable:**

```bash
echo "VAR_NAME=value" >> $GITHUB_ENV
```

**Add to PATH:**

```bash
echo "/path/to/bin" >> $GITHUB_PATH
```

**Set step summary:**

```bash
echo "## Summary" >> $GITHUB_STEP_SUMMARY
echo "Build succeeded" >> $GITHUB_STEP_SUMMARY
```

**Group logs:**

```bash
echo "::group::Group name"
echo "Content"
echo "::endgroup::"
```

**Mask value (secret):**

```bash
echo "::add-mask::$SECRET_VALUE"
```

### Debugging

**Enable debug logging:**

Set repository secret: `ACTIONS_STEP_DEBUG=true`

**Enable runner diagnostic logging:**

Set repository secret: `ACTIONS_RUNNER_DEBUG=true`

---

## Complete Example

```yaml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
    paths:
      - 'src/**'
      - 'package.json'
  pull_request:
    branches: [main]
  workflow_dispatch:
    inputs:
      environment:
        type: choice
        options: [dev, staging, production]

env:
  NODE_ENV: production

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read
  pull-requests: write

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node: [18, 20, 22]
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'

      - run: npm ci

      - run: npm test

      - if: matrix.node == 20
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    permissions:
      contents: write
      deployments: write
    steps:
      - uses: actions/checkout@v5

      - run: ./deploy.sh
        env:
          API_KEY: ${{ secrets.API_KEY }}
```

---

For working examples, see the `examples/` directory.

```

writing-github-actions | SkillHub