Actions Authoring

SkillAI & models

This skill should be used when writing, hardening, or optimizing GitHub Actions workflows, permissions, action pinning, script injection, caching, matrices, concurrency, and cost.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Actions Authoring skill

What this skill tells your AI

The instructions your AI receives, as published by thelobbi/claude in plugins/delivery-orchestrator/skills/actions-authoring/SKILL.md and read by ahel’s review.

Security first — the three that actually get exploited

1. pull_request_target with a PR-head checkout

# CRITICAL — a fork PR gets write-scoped secrets
on: pull_request_target
jobs:
  test:
    steps:
      - uses: actions/checkout@v4
        with: { ref: ${{ github.event.pull_request.head.sha }} }
      - run: npm test        # attacker's code, your secrets

pull_request_target runs in the base repo's context with secrets. Checking out the PR head executes attacker-controlled code with those secrets in scope. Use pull_request and accept that secrets are unavailable — that is the point.

2. Script injection through ${{ }}

# wrong — issue title is attacker-controlled, interpolated into the shell
- run: echo "Title: ${{ github.event.issue.title }}"

# right — passed as an environment variable
- env:
    TITLE: ${{ github.event.issue.title }}
  run: echo "Title: $TITLE"

Any github.event.* field a user can write is untrusted: title, body, branch name, commit message, label name.

3. Unpinned third-party actions

Tags are mutable. Pin to a commit SHA with the version in a trailing comment:

- uses: some-org/some-action@8f4b7c2e9a1d... # v3.1.0

First-party actions/* at a major tag is the common accepted exception — decide deliberately rather than by default.

Permissions

Declare explicitly. The default GITHUB_TOKEN scope is far wider than any single workflow needs.

permissions:
  contents: read          # workflow-level floor

jobs:
  release:
    permissions:
      contents: write     # narrowed per job
      id-token: write     # OIDC

Use OIDC for cloud auth. Long-lived cloud keys in secrets are a standing liability that no rotation policy fully fixes.

Caching

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

Two failure modes, both common:

  • A key containing github.run_id, a timestamp, or the commit sha can never hit — it costs upload time every run and saves nothing.
  • A key with no lockfile hash never misses when it should, restoring a stale dependency tree after an upgrade.

Check the actual hit rate in the restore step's log before tuning anything.

Concurrency

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

Never cancel in progress on the default branch — that kills the run that gates deploys.

Matrices

Full OS × version matrix on every PR is usually waste. Run a representative slice on PRs and the full matrix on the default branch and on a schedule.

strategy:
  fail-fast: false        # you want all failures, not the first
  matrix:
    node: [20, 22]
    include:
      - { node: 24, os: ubuntu-latest, experimental: true }

Cost levers, ordered by typical payoff

Cache hit rate → job graph (unnecessary needs:) → matrix pruning → concurrency groups → path filters → fail-fast ordering (cheap checks first) → runner sizing → artifact retention.

Never buy speed by dropping coverage. A removed matrix entry that covers a supported platform is not an optimization.

See also

  • ci-forensics — diagnosing failures
  • ../commands/audit.md — repo-wide workflow security audit

Signals

GitHub stars
21
Forks
2
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
actions-authoring
Source
github.com/thelobbi/claude