home / skills / arustydev / ai / cicd-github-actions-dev

cicd-github-actions-dev skill

/components/skills/cicd-github-actions-dev

This skill helps you create, debug, and optimize GitHub Actions workflows and CI configurations to accelerate reliable software delivery.

npx playbooks add skill arustydev/ai --skill cicd-github-actions-dev

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
14.9 KB
---
name: cicd-github-actions-dev
description: Develop and troubleshoot GitHub Actions workflows and CI configurations. Use when creating workflows, debugging CI failures, understanding job logs, or optimizing CI pipelines.
---

# GitHub Actions CI Development

Guide for developing, debugging, and optimizing GitHub Actions workflows and CI configurations.

## When to Use This Skill

- Creating new GitHub Actions workflows
- Debugging CI failures from job logs
- Understanding workflow syntax and features
- Optimizing CI performance
- Troubleshooting permission or environment issues

## Workflow Structure

### File Location

Workflows live in `.github/workflows/` with `.yml` or `.yaml` extension.

### Basic Structure

```yaml
name: Workflow Name

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

jobs:
  job-name:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Step name
        run: echo "Hello"
```

## Debugging CI Failures

### Step 1: Get the Job Log URL

When a PR check fails, the user typically provides a URL like:
`https://github.com/owner/repo/actions/runs/12345/job/67890`

### Step 2: Fetch and Analyze Logs

Use GitHub CLI to get detailed logs:

```bash
gh run view <run-id> --log-failed
```

Or fetch specific job logs:

```bash
gh api repos/owner/repo/actions/jobs/<job-id>/logs
```

### Step 3: Identify the Failure Point

Look for:
- Exit codes (non-zero indicates failure)
- Error messages in red/highlighted text
- The specific step that failed
- Environment or dependency issues

### Step 4: Reproduce Locally

Always try to reproduce the failure locally before pushing fixes:

```bash
# For Homebrew taps
brew test-bot --only-tap-syntax

# For Node projects
npm ci && npm test

# For general linting
<linter> --config <config-file> <files>
```

## Common CI Patterns

### Matrix Builds

Test across multiple OS/versions:

```yaml
jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        node: [18, 20]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
```

### Conditional Steps

```yaml
- name: Only on main
  if: github.ref == 'refs/heads/main'
  run: echo "On main branch"

- name: Only on PR
  if: github.event_name == 'pull_request'
  run: echo "This is a PR"
```

### Caching Dependencies

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

### Artifacts

Upload build outputs:

```yaml
- uses: actions/upload-artifact@v4
  with:
    name: build-output
    path: dist/
```

## Homebrew-Specific CI

### Homebrew Test Bot

The standard CI for Homebrew taps uses `Homebrew/actions/build-bottle`:

```yaml
name: Test Formula

on:
  pull_request:
    paths:
      - 'Formula/**'

jobs:
  test-bot:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-22.04, macos-latest]
    steps:
      - uses: actions/checkout@v4
      - uses: Homebrew/actions/setup-homebrew@master
      - run: brew test-bot --only-tap-syntax
      - run: brew test-bot --only-formulae
```

### Common Homebrew CI Failures

| Failure | Cause | Solution |
|---------|-------|----------|
| `brew style` errors | Rubocop violations | Run `brew test-bot --only-tap-syntax` locally |
| Ruby in markdown | rubocop-md lints `ruby` code fences | Use `text` fence language instead |
| Formula audit errors | Missing fields or bad values | Run `brew audit --new --formula <name>` |
| Test failures | Test block issues | Verify test block creates needed files |

## Troubleshooting Techniques

### Check Workflow Syntax

```bash
# Validate YAML syntax
yamllint .github/workflows/

# Check with actionlint (if installed)
actionlint .github/workflows/
```

### View Recent Runs

```bash
gh run list --limit 10
gh run view <run-id>
gh run view <run-id> --log
```

### Re-run Failed Jobs

```bash
gh run rerun <run-id> --failed
```

### Check PR Status

```bash
gh pr view <pr-number> --json statusCheckRollup
```

### Watch CI Progress

```bash
gh run watch <run-id>
```

## Environment and Secrets

### Using Secrets

```yaml
env:
  API_KEY: ${{ secrets.API_KEY }}
```

### GitHub Token

The `GITHUB_TOKEN` is automatically available:

```yaml
env:
  GH_TOKEN: ${{ github.token }}
```

### Environment Variables

```yaml
env:
  NODE_ENV: production

jobs:
  build:
    env:
      CI: true
    steps:
      - env:
          STEP_VAR: value
        run: echo $STEP_VAR
```

## Performance Optimization

### Parallel Jobs

Jobs run in parallel by default. Use `needs` for dependencies:

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

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

  deploy:
    needs: [lint, test]  # Waits for both
    runs-on: ubuntu-latest
    steps: [...]
```

### Path Filtering

Only run on relevant changes:

```yaml
on:
  push:
    paths:
      - 'src/**'
      - 'package.json'
    paths-ignore:
      - '**.md'
      - 'docs/**'
```

### Concurrency Control

Cancel redundant runs:

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

## Debugging Checklist

- [ ] Read the full error message in job logs
- [ ] Identify which step failed
- [ ] Check if it's a flaky test or consistent failure
- [ ] Reproduce locally with same commands
- [ ] Verify all dependencies and versions match
- [ ] Check for environment-specific issues (OS, permissions)
- [ ] Review recent changes that might have caused the failure
- [ ] Push fix and verify CI passes

## Local Testing with `act`

[nektos/act](https://github.com/nektos/act) runs GitHub Actions locally using Docker, enabling fast iteration without pushing to GitHub.

### Installation

```bash
# macOS
brew install act

# Other platforms
curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
```

### Basic Usage

```bash
# Run all workflows triggered by push
act push

# Run all workflows triggered by pull_request
act pull_request

# Run a specific job
act -j build

# Run with a specific event payload
act pull_request -e event.json

# List available jobs without running
act -l

# Run with verbose output
act -v
```

### Secrets and Variables

```bash
# Pass secrets via file
act --secret-file .secrets

# Pass individual secrets
act -s GITHUB_TOKEN="$(gh auth token)"

# Pass environment variables
act --env-file .env
```

### Runner Images

```bash
# Use medium image (default, smaller but missing some tools)
act -P ubuntu-latest=catthehacker/ubuntu:act-latest

# Use large image (closer to GitHub runners, ~12GB)
act -P ubuntu-latest=catthehacker/ubuntu:full-latest

# Use micro image (fastest, minimal tools)
act -P ubuntu-latest=node:16-buster-slim
```

### Limitations

| Limitation | Workaround |
|------------|------------|
| Some actions don't work | Use `-P` to specify compatible runner images |
| No macOS/Windows runners | Test OS-specific code on actual GitHub runners |
| Service containers differ | May need Docker Compose for complex setups |
| GitHub context differences | Some `github.*` values unavailable locally |
| Large image downloads | Use micro images for simple workflows |

### When to Use `act`

- **Use `act`:** Rapid iteration, testing matrix logic, validating workflow syntax, testing secret handling
- **Skip `act`:** OS-specific tests, actions requiring GitHub API, final validation before merge

## Pre-commit Hooks for Workflows

Catch workflow errors before commit to reduce failed CI runs.

### actionlint Hook

The most important hook - catches syntax errors, type mismatches, and common mistakes.

```yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/rhysd/actionlint
    rev: v1.7.4
    hooks:
      - id: actionlint
        files: ^\.github/workflows/
```

### yamllint Hook

Validates YAML structure and formatting.

```yaml
repos:
  - repo: https://github.com/adrienverge/yamllint
    rev: v1.35.1
    hooks:
      - id: yamllint
        files: ^\.github/workflows/
        args: [--config-file, .yamllint.yml]
```

Recommended `.yamllint.yml` for GitHub Actions:

```yaml
extends: default
rules:
  line-length:
    max: 120
  truthy:
    check-keys: false  # Allows 'on:' without quotes
  comments:
    min-spaces-from-content: 1
```

### check-yaml Hook

Basic YAML validity check.

```yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: check-yaml
        files: ^\.github/workflows/
        args: [--unsafe]  # Required for GitHub Actions syntax
```

### Complete Pre-commit Config

```yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: check-yaml
        files: ^\.github/workflows/
        args: [--unsafe]

  - repo: https://github.com/adrienverge/yamllint
    rev: v1.35.1
    hooks:
      - id: yamllint
        files: ^\.github/workflows/
        args: [-c, .yamllint.yml]

  - repo: https://github.com/rhysd/actionlint
    rev: v1.7.4
    hooks:
      - id: actionlint
```

### Manual Validation

```bash
# Run actionlint on all workflows
actionlint

# Run on specific file
actionlint .github/workflows/ci.yml

# With shellcheck integration (recommended)
actionlint -shellcheck=$(which shellcheck)
```

## Claude Hooks for Workflow Development

Configure Claude Code hooks to automatically validate workflows during development.

### Post-Edit Hook: actionlint

Run actionlint after editing workflow files.

```json
// .claude/settings.json
{
  "hooks": {
    "post_edit": [
      {
        "pattern": "^\\.github/workflows/.*\\.ya?ml$",
        "command": "actionlint",
        "description": "Lint GitHub Actions workflow"
      }
    ]
  }
}
```

### Pre-Commit Hook: Full Validation

Validate before committing workflow changes.

```json
{
  "hooks": {
    "pre_commit": [
      {
        "pattern": "^\\.github/workflows/.*\\.ya?ml$",
        "command": "actionlint && yamllint .github/workflows/",
        "description": "Validate GitHub Actions workflows"
      }
    ]
  }
}
```

### Suggested Claude Workflow

1. Edit workflow file
2. Hook runs actionlint automatically
3. Fix any reported issues
4. Commit triggers pre-commit hooks
5. Push with confidence - first CI run more likely to pass

## Reusable Actions and Workflows

### Action Source Priority

Always prefer actions from `arustydev/gha` for consistency and control.

**Priority order:**
1. `arustydev/gha` - First choice for all actions
2. Third-party action - Temporary fallback with issue tracking
3. Local development - When no suitable action exists

### Using arustydev/gha Actions

```yaml
steps:
  - uses: arustydev/gha/setup-node@v1
  - uses: arustydev/gha/deploy-preview@v1
```

### When Action Not in arustydev/gha

If a needed action exists from a third party but not in `arustydev/gha`:

1. **Create tracking issue:**
   ```bash
   gh issue create --repo arustydev/gha \
     --title "[ACTION] Add <action-name>" \
     --body "Third-party equivalent: <owner>/<action>@<version>

   Currently using third-party version in: <project-name>

   Requested functionality: <description>"
   ```

2. **Use third-party temporarily:**
   ```yaml
   steps:
     # TODO: Replace with arustydev/gha/<action> when available
     # Tracking: https://github.com/arustydev/gha/issues/XX
     - uses: third-party/action@v1
   ```

### When No Suitable Action Exists

If no action (arustydev/gha or third-party) meets the need:

1. **Create needs issue:**
   ```bash
   gh issue create --repo arustydev/gha \
     --title "[ACTION] Need <action-name>" \
     --body "## Use Case
   <describe the need>

   ## Proposed Solution
   <high-level approach>

   ## Initial Development
   Will develop locally in: <project-name>"
   ```

2. **Develop locally in the project:**
   ```
   .github/
   └── actions/
       └── my-action/
           ├── action.yml
           ├── package.json
           ├── tsconfig.json
           └── src/
               └── index.ts
   ```

3. **Use TypeScript/Node for development:**
   ```yaml
   # .github/actions/my-action/action.yml
   name: My Action
   description: Does something useful
   inputs:
     example:
       description: Example input
       required: true
   runs:
     using: node20
     main: dist/index.js
   ```

   ```typescript
   // .github/actions/my-action/src/index.ts
   import * as core from '@actions/core';
   import * as github from '@actions/github';

   async function run(): Promise<void> {
     try {
       const example = core.getInput('example', { required: true });
       core.info(`Processing: ${example}`);
       // Action logic here
     } catch (error) {
       if (error instanceof Error) {
         core.setFailed(error.message);
       }
     }
   }

   run();
   ```

4. **Reference locally during development:**
   ```yaml
   steps:
     - uses: actions/checkout@v4
     - uses: ./.github/actions/my-action
       with:
         example: value
   ```

5. **When functional, open PR to arustydev/gha:**
   ```bash
   # Copy action to gha repo
   cp -r .github/actions/my-action ~/repos/gha/actions/

   # Create PR
   cd ~/repos/gha
   git checkout -b feat/add-my-action
   git add actions/my-action
   git commit -m "feat(action): add my-action"
   git push -u origin feat/add-my-action
   gh pr create --title "feat(action): add my-action" \
     --body "Closes #XX

   Developed and tested in: <project-name>"
   ```

6. **After merge, update the original project:**
   ```yaml
   steps:
     - uses: arustydev/gha/my-action@v1  # Now using centralized version
       with:
         example: value
   ```

   Remove the local `.github/actions/my-action/` directory.

### Reusable Workflows

For complex multi-job workflows, use reusable workflows:

```yaml
# In arustydev/gha/.github/workflows/node-ci.yml
name: Node CI
on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '20'

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

```yaml
# In your project
jobs:
  ci:
    uses: arustydev/gha/.github/workflows/node-ci.yml@main
    with:
      node-version: '20'
```

## References

- [GitHub Actions Documentation](https://docs.github.com/en/actions)
- [Workflow Syntax Reference](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions)
- [GitHub CLI Manual](https://cli.github.com/manual/)
- [Homebrew/actions](https://github.com/Homebrew/actions)
- [actionlint](https://github.com/rhysd/actionlint) - GitHub Actions linter
- [nektos/act](https://github.com/nektos/act) - Run GitHub Actions locally
- [arustydev/gha](https://github.com/arustydev/gha) - Centralized reusable actions
- [@actions/core](https://github.com/actions/toolkit/tree/main/packages/core) - Action toolkit for TypeScript
- [Creating JavaScript Actions](https://docs.github.com/en/actions/creating-actions/creating-a-javascript-action)

Overview

This skill helps you develop, debug, and optimize GitHub Actions workflows and CI configurations. It focuses on practical steps: validating workflow syntax, inspecting job logs, reproducing failures locally, and improving CI performance. Use it to write new workflows, troubleshoot failing jobs, and standardize CI patterns for reliability.

How this skill works

I inspect workflow files in .github/workflows, analyze job run logs and step output, and recommend fixes based on exit codes, error messages, and environment differences. I guide local reproduction using tools like gh, act, and common local commands, suggest linting and pre-commit hooks, and propose performance improvements such as caching, matrix tuning, and concurrency controls.

When to use it

  • Creating or updating GitHub Actions workflows
  • Debugging failing or flaky CI jobs from PRs or runs
  • Validating workflow syntax and pre-commit checks
  • Reproducing CI failures locally with act or Docker
  • Optimizing CI speed, caching, and parallelism

Best practices

  • Keep workflows in .github/workflows with clear names and small jobs
  • Validate YAML and workflow semantics with yamllint and actionlint pre-commit hooks
  • Reproduce failures locally before pushing using gh, act, or the same commands used in steps
  • Use caching and matrix strategies sparingly to balance coverage and runtime costs
  • Prefer reusable workflows and centralized actions for consistency; track third-party actions and replace with vetted versions

Example use cases

  • Debug a failing job by fetching logs with gh run view and identifying the failing step
  • Add a matrix job to test multiple OS and runtimes, while caching dependencies to speed runs
  • Set up actionlint and yamllint in pre-commit to catch syntax and schema errors before pushing
  • Use act to iterate on a workflow locally, passing secrets and event payloads for realistic tests
  • Create a reusable workflow for cross-repo CI to standardize tests and deployments

FAQ

How do I get detailed logs for a failed run?

Use gh run view <run-id> --log-failed or gh api repos/owner/repo/actions/jobs/<job-id>/logs to fetch full job logs and inspect the failing step and exit codes.

When should I use act vs real GitHub runners?

Use act for rapid iteration, matrix logic checks, and syntax validation. Always run final tests on GitHub runners for macOS/windows-specific behavior, actions requiring GitHub API, or full integration validation.