Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,4 @@ Located in `.agents/skills/`. Reference for detailed patterns:
| [kotlin-multiplatform](./skills/kotlin-multiplatform/SKILL.md) | KMP patterns, expect/actual | KMP modules |
| [frontend-design](./skills/frontend-design/SKILL.md) | Create production-grade frontend UI with strong visual direction while avoiding generic AI patterns | Building or refining web components, pages, dashboards, or application UI |
| [conventional-commits](./skills/conventional-commits/SKILL.md) | Conventional Commits specification | Creating commits, git messages |
| [github-actions](./skills/github-actions/SKILL.md) | GitHub Actions CI/CD best practices, security, optimization | `.github/workflows/*.yml`, CI/CD pipelines |
225 changes: 225 additions & 0 deletions .agents/skills/github-actions/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
---
name: github-actions
description: >
GitHub Actions CI/CD best practices for workflow design, security hardening, and pipeline optimization.
Trigger: When creating, reviewing, or auditing GitHub Actions workflows (.github/workflows/*.{yml,yaml}),
fixing CI/CD issues, or hardening pipeline security.
license: Apache-2.0
metadata:
author: "@dallay"
version: "1.0"
---

# GitHub Actions CI/CD Skill

Expert guidance for designing, securing, and optimizing GitHub Actions CI/CD pipelines.

## When to Use

- Creating or modifying `.github/workflows/*.{yml,yaml}` files
- Auditing existing CI/CD pipelines for security, performance, or reliability issues
- Fixing workflow triggers, permissions, or caching problems
- Setting up deployment pipelines (staging, production, rollback)
- Integrating security scanning (SAST, SCA, secret scanning) into CI
- Optimizing workflow execution time (caching, matrix, parallelism)

## Critical Patterns

### Security (Non-Negotiable)

- **Pin actions to full SHA**: Always use `uses: owner/action@<40-char-sha> # vX.Y.Z`. Tags
(`@v4`, `@main`) are mutable and vulnerable to supply chain attacks. Add version as comment for
readability.
- **Least-privilege permissions**: Set `permissions: {}` or `contents: read` at workflow level.
Override per-job only when writes are needed. Never leave permissions at default (broad write).
- **Secrets via `secrets.*` only**: Never hardcode sensitive data. Use environment-specific secrets
for deployment workflows with manual approval gates.
- **OIDC over static credentials**: For cloud auth (AWS, Azure, GCP), prefer OIDC short-lived
tokens over long-lived access keys stored as secrets.
- **Audit third-party actions**: Prefer `actions/` org. Review source for external actions. Enable
Dependabot for action version updates.
- **`pull_request_target` caution**: This trigger runs with write permissions against the base repo.
Never checkout untrusted PR code and execute it in this context.

### Workflow Structure

- **Descriptive names**: Workflow `name` and step `name` must be clear for log readability.
- **Concurrency control**: Use `concurrency` with `cancel-in-progress: true` to prevent duplicate
runs and waste.
- **Conditional execution**: Use `if` conditions for branch-specific, actor-specific, or
event-specific logic.
- **Reusable workflows**: Extract common patterns into `workflow_call` workflows (prefix with `_`
for internal ones, e.g., `_publish.yml` or `_publish.yaml`).
- **Timeout**: Set `timeout-minutes` on long-running jobs to prevent hung runners.

### Performance

- **Caching**: Use `actions/cache` (pinned SHA) with `hashFiles` keys for package managers and
build outputs. Use `restore-keys` for fallback.
- **Shallow clone**: Use `fetch-depth: 1` unless full history is needed (release tagging, blame).
- **Matrix parallelization**: Use `strategy.matrix` with `fail-fast: false` for comprehensive
multi-env testing.
- **Artifacts for inter-job data**: Use `actions/upload-artifact` / `actions/download-artifact`
(pinned SHAs) with appropriate `retention-days`.

### Deployment

- **Environment protection**: Use GitHub `environment` with required reviewers, branch restrictions,
and deployment URLs.
- **Rollback strategy**: Keep previous artifacts. Implement automated rollback on health check
failure.
- **Version validation**: Verify version consistency across manifests before publishing.
- **Idempotent publishes**: Check if version already exists before publishing to registries.

## Decision Tables

### When to Use `fetch-depth: 0` (Full History)

| Use Case | `fetch-depth` |
|-----------------------------|---------------|
| Standard build/test | `1` |
| Release tagging/changelog | `0` |
| Commit message validation | `0` |
| CodeQL / deep analysis | `0` or omit |
| Dependency lockfile fix | `0` |

### Choosing Deployment Strategy

| Risk Tolerance | Strategy | Rollback Speed |
|----------------|-----------------|----------------|
| Low | Blue/Green | Instant |
| Medium | Canary | Fast |
| High | Rolling Update | Moderate |
| Feature flags | Dark Launch | Instant |

### Permission Mapping

| Workflow Need | Permission Required |
|----------------------------------|------------------------------|
| Read code only | `contents: read` |
| Push commits / create tags | `contents: write` |
| Comment on / update PRs | `pull-requests: write` |
| Upload SARIF security results | `security-events: write` |
| Deploy to GitHub Pages | `pages: write`, `id-token: write` |
| Publish to GitHub Packages | `packages: write` |
| Create/update issues | `issues: write` |
| Manage caches | `actions: write` |

## Code Examples

### Secure Workflow Skeleton

```yaml
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: CI

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

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
# ... build steps
```

### Effective Caching Pattern

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

### Environment-Protected Deployment

```yaml
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://prod.example.com
permissions:
contents: read
id-token: write # For OIDC
steps:
- name: Configure cloud credentials (OIDC)
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
role-to-assume: arn:aws:iam::123456789:role/deploy
aws-region: us-east-1
```

## Audit Checklist

When reviewing workflows, check:

1. **All `uses:` pinned to full SHA** with version comment
2. **`permissions:` explicitly set** (workflow + job level)
3. **`concurrency:` configured** for non-trivial workflows
4. **`timeout-minutes:` set** on long-running jobs
5. **Secrets accessed via `secrets.*`** only, never in logs
6. **`fetch-depth: 1`** unless full history needed
7. **Caching configured** for package manager dependencies
8. **`if` conditions** for conditional steps (bot exclusions, branch filters)
9. **Environment protection** for deployment jobs
10. **No mutable action refs** (`@v4`, `@main`, `@latest`)

## Troubleshooting Quick Reference

| Symptom | Likely Cause | Fix |
|--------------------------------|--------------------------------------|-----------------------------------------------|
| Workflow not triggering | Wrong `on` trigger or path filter | Verify branch/path/event config |
| `Resource not accessible` | Missing `permissions` | Add required permission at job level |
| Cache miss every run | Dynamic cache key | Use `hashFiles` for stable keys |
| Slow checkout | `fetch-depth: 0` on large repo | Use `fetch-depth: 1` |
| Flaky tests in CI | Race conditions, env differences | Use explicit waits, `services` for deps |
| `pull_request_target` exploit | Running untrusted code with perms | Never checkout PR head in `_target` trigger |

## Commands

```bash
# Validate workflow YAML syntax locally
npx yaml-lint .github/workflows/*.{yml,yaml}
Comment thread
yacosta738 marked this conversation as resolved.

# Mirror CodeRabbit's GitHub Actions validation locally with actionlint
# using the repository rule configuration in .github/actionlint.yml
actionlint -config-file .github/actionlint.yml .github/workflows/*.{yml,yaml}

# List action versions and check for updates
gh api repos/actions/checkout/releases/latest --jq '.tag_name'

# Resolve tag to immutable SHA (for pinning)
git ls-remote --tags https://github.com/actions/checkout.git | rg "refs/tags/v6.0.2(\^\{\})?$" | sort | tail -n 1 | awk '{print $1}'

# Check workflow run status (use workflow name or either file extension)
gh run list --workflow="PR Checks" --limit=5
```

## Resources

- [GitHub Actions Security Hardening](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions)
- [GitHub Actions Reusable Workflows](https://docs.github.com/en/actions/sharing-automations/reusing-workflows)
- See [pinned-tag skill](../pinned-tag/SKILL.md) for SHA resolution workflow
10 changes: 6 additions & 4 deletions .github/workflows/_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,14 @@ jobs:
if: ${{ inputs.release }}
continue-on-error: true
env:
SIGNING_KEY: ${{ secrets.SIGNING_IN_MEMORY_KEY }}
MAVEN_USER: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
run: |
if [ -z "${{ secrets.SIGNING_IN_MEMORY_KEY }}" ] || [ -z "${{ secrets.MAVEN_CENTRAL_USERNAME }}" ]; then
if [ -z "$SIGNING_KEY" ] || [ -z "$MAVEN_USER" ]; then
echo "⚠️ Skipping Maven Central publish - required secrets not configured"
exit 0
fi
Expand All @@ -208,7 +210,7 @@ jobs:
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
if [ -z "${{ secrets.CARGO_REGISTRY_TOKEN }}" ]; then
if [ -z "$CARGO_REGISTRY_TOKEN" ]; then
echo "⚠️ Skipping crates.io publish - CARGO_REGISTRY_TOKEN not configured"
exit 0
fi
Expand Down Expand Up @@ -322,7 +324,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: 📦 Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
Expand Down Expand Up @@ -398,7 +400,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: 📦 Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/auto-fix-lockfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,21 @@ jobs:
with:
fetch-depth: 0

- name: 📦 Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
with:
version: 10

- name: 📦 Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24"
cache: "pnpm"
cache-dependency-path: |
pnpm-lock.yaml
clients/web/pnpm-lock.yaml
clients/web/apps/docs/pnpm-lock.yaml
clients/web/apps/marketing/pnpm-lock.yaml

- name: ☕ Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/cleanup-cache.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Cleanup caches

on:
Expand All @@ -10,5 +11,5 @@ permissions:

jobs:
cleanup:
uses: dallay/common-actions/.github/workflows/cleanup-cache.yml@main
uses: dallay/common-actions/.github/workflows/cleanup-cache.yml@e77aee209c07cd031e1fc6a3f275df774b4685a2 # v1.1.0
secrets: inherit
11 changes: 11 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,21 @@ jobs:
- name: ✈ Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: 📦 Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
with:
version: 10

- name: 📦 Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24"
cache: "pnpm"
cache-dependency-path: |
pnpm-lock.yaml
clients/web/pnpm-lock.yaml
clients/web/apps/docs/pnpm-lock.yaml
clients/web/apps/marketing/pnpm-lock.yaml

- name: ☕ Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/contributor-report.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Contributor Report

on:
Expand All @@ -10,5 +11,5 @@ permissions:

jobs:
report:
uses: dallay/common-actions/.github/workflows/contributor-report.yml@main
uses: dallay/common-actions/.github/workflows/contributor-report.yml@e77aee209c07cd031e1fc6a3f275df774b4685a2 # v1.1.0
secrets: inherit
2 changes: 1 addition & 1 deletion .github/workflows/core-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
- name: ✈ Checkout default branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
fetch-depth: 1

- name: ☕ Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/detekt.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
Expand Down Expand Up @@ -44,6 +45,7 @@ jobs:

# Required for upload-sarif to push security events
permissions:
contents: read
security-events: write

# Steps represent a sequence of tasks that will be executed as part of the job
Expand Down Expand Up @@ -122,7 +124,7 @@ jobs:
)" > "${{ github.workspace }}/detekt.sarif.json"

# Uploads results to GitHub repository using the upload-sarif action
- uses: github/codeql-action/upload-sarif@v4
- uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
with:
# Path to SARIF file relative to the root of the repository
sarif_file: ${{ github.workspace }}/detekt.sarif.json
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/fix-renovate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,21 @@ jobs:
exit 1
fi

- name: 📦 Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
with:
version: 10

- name: 📦 Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24"
cache: "pnpm"
cache-dependency-path: |
pnpm-lock.yaml
clients/web/pnpm-lock.yaml
clients/web/apps/docs/pnpm-lock.yaml
clients/web/apps/marketing/pnpm-lock.yaml

- name: ☕ Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
Expand Down
Loading
Loading