Recipes for Claude Code

Agent Automation Recipes

A curated library of trigger-driven agent workflows — nightly dependency sweeps, CI-failure repair, issue triage, PR review, security scans. Each recipe ships a complete workflow file, its trigger, and the contract it guarantees.

An automation is the layer that fires a loop. For the discipline of designing what happens once it fires — contracts, checkers, stop conditions — read the Loop Engineering guide.

01Shapes

Four Loop Shapes

Almost every production automation is one of four shapes. Each recipe below is tagged with the shape it belongs to, echoing the Loops in the Wild taxonomy.

Scheduled maintenance

scheduled-maintenance

Cron-triggered upkeep — dependency bumps, doc-drift checks, stale sweeps. Opens a PR or files an issue only when there's work.

CI-triggered repair

ci-triggered-repair

A failing build wakes the loop with the error log as its contract: reproduce, fix, verify green. Bounded retries, then escalate.

Issue-driven feature

issue-driven-feature

The backlog is the queue. Each issue becomes a contract; triage and first-pass work run automatically, humans review results.

Review & audit sweep

review-audit-sweep

Loop-until-dry over the codebase: validation lanes, PR review, spec-consistency guards that block a bad merge.

02Library

18 Automation Recipes

Each card carries a complete workflow with a copy button, its trigger and loop shape, the contract it guarantees, requirements, and at least one real repository where the pattern appears. Deep-link any recipe with its anchor, e.g. /automations#ci-failure-repair.

schedulegithub-actions

Dependencies drift out of date between manual bumps, and nobody remembers to run the update. A nightly job keeps the lockfile current and opens a PR only when there is actually something to merge.

Recipe · yaml
name: nightly-dependency-sweep
on:
  schedule:
    - cron: '0 3 * * *'   # 03:00 UTC daily
  workflow_dispatch:

permissions:
  contents: write
  pull-requests: write

jobs:
  sweep:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Update dependencies
        run: |
          npm update --save
          npm audit fix || true
      - name: Run tests before proposing the bump
        run: npm test --silent
      - name: Open PR if the lockfile changed
        uses: peter-evans/create-pull-request@v6
        with:
          branch: deps/nightly-sweep
          title: 'chore(deps): nightly dependency sweep'
          commit-message: 'chore(deps): nightly dependency sweep'
          body: 'Automated nightly dependency update. Tests passed before this PR was opened.'
          delete-branch: true

Contract: A PR is opened only when dependencies changed AND the test suite passed; a clean tree produces no PR and no noise.

Requirements

  • GitHub Actions enabled
  • peter-evans/create-pull-request action
  • A passing test command to gate the bump

Seen in

pr-opengithub-actions

Dependabot floods the queue with bumps; reviewing every patch by hand is toil, but blindly auto-merging majors is dangerous. Gate the merge on semver severity so minor/patch flow through and majors wait for a human.

Recipe · yaml
name: dependabot-automerge
on:
  pull_request_target:
    types: [opened, reopened, synchronize, ready_for_review]

permissions:
  contents: write
  pull-requests: write

jobs:
  automerge:
    if: github.actor == 'dependabot[bot]'
    runs-on: ubuntu-latest
    steps:
      - name: Fetch update metadata
        id: meta
        uses: dependabot/fetch-metadata@v2
      - name: Auto-merge minor and patch updates
        if: steps.meta.outputs.update-type != 'version-update:semver-major'
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Hold majors for human review
        if: steps.meta.outputs.update-type == 'version-update:semver-major'
        run: |
          gh pr edit "$PR_URL" --add-label 'dependabot-major-held'
          gh pr comment "$PR_URL" --body 'Major version bump held for manual review.'
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Contract: Minor/patch bumps auto-merge once CI is green; major bumps are labeled and left open for a human, never merged automatically.

Requirements

  • Dependabot configured for the repo
  • dependabot/fetch-metadata action
  • Branch protection so --auto waits for required checks

Seen in

ci-failureclaude-codegithub-actions

A red build blocks the team until someone free-diagnoses it. Wake an agent with the failure logs as its contract: reproduce, fix on a branch, and push — bounded so it can't thrash forever.

Recipe · yaml
name: ci-failure-repair
on:
  workflow_run:
    workflows: ['CI']
    types: [completed]

permissions:
  contents: write
  pull-requests: write
  actions: read
  id-token: write

jobs:
  repair:
    # Only on failure, only for PR runs, and never on the fix branch itself.
    if: >-
      github.event.workflow_run.conclusion == 'failure' &&
      github.event.workflow_run.pull_requests[0] &&
      !startsWith(github.event.workflow_run.head_branch, 'claude-auto-fix-ci-')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.workflow_run.head_branch }}
          fetch-depth: 0
      - name: Create fix branch
        run: git checkout -b "claude-auto-fix-ci-${{ github.run_id }}"
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          prompt: |
            The CI workflow failed on this branch. Download the failing job logs
            for run ${{ github.event.workflow_run.id }} with `gh run view`,
            reproduce the failure locally, fix the root cause, and verify the
            build is green. Keep the change minimal.
          claude_args: '--max-turns 15'
      - name: Push the fix branch
        run: git push origin HEAD

Contract: Fires only on CI failure for a PR; the fix lands on a dedicated `claude-auto-fix-ci-*` branch (never the source branch), and the guard prevents the repair run from re-triggering itself.

Requirements

  • anthropics/claude-code-action + ANTHROPIC_API_KEY
  • A workflow named 'CI' to watch
  • Bounded --max-turns so the loop can't thrash

Seen in

ci-failuregithub-actions

Flaky end-to-end tests fail on stale locators, and diagnosing which selector broke is tedious. On an E2E failure, parse the report and post concrete locator-fix suggestions to the PR — advisory, so it never blocks the merge.

Recipe · yaml
name: e2e-failure-healer
on:
  workflow_run:
    workflows: ['E2E Tests']
    types: [completed]

permissions:
  contents: read
  pull-requests: write
  actions: read

jobs:
  heal:
    if: github.event.workflow_run.conclusion == 'failure'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Download the test report artifact
        uses: actions/download-artifact@v4
        with:
          name: e2e-report
          run-id: ${{ github.event.workflow_run.id }}
          github-token: ${{ secrets.GITHUB_TOKEN }}
      - name: Post healing suggestions to the PR
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const results = JSON.parse(fs.readFileSync('results.json', 'utf8'));
            const failed = results.tests.filter(t => t.status === 'failed');
            if (!failed.length) return;
            const body = ['### E2E healer suggestions', ...failed.map(
              t => `- \`${t.title}\`: check locator \`${t.locator}\` — it may have drifted.`
            )].join('\n');
            const pr = context.payload.workflow_run.pull_requests[0];
            if (pr) await github.rest.issues.createComment({
              ...context.repo, issue_number: pr.number, body
            });

Contract: Advisory only: posts locator-fix suggestions from the failing report as a PR comment and never blocks the merge.

Requirements

  • An E2E job that uploads a results.json artifact named 'e2e-report'
  • pull-requests: write permission

Seen in

pr-openclaude-codegithub-actions

Human reviewers are the bottleneck, and small PRs wait hours for a first pass. Run an automated deep review on every PR open/update so the obvious issues are caught before a person looks.

Recipe · yaml
name: pr-review-on-open
on:
  pull_request:
    types: [opened, synchronize, ready_for_review, reopened]

permissions:
  contents: read
  pull-requests: read
  id-token: write

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 1
      - name: Claude code review
        # SHA-pin the action so a moved @v1 tag can't inject code into CI.
        uses: anthropics/claude-code-action@v1
        with:
          claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
          prompt: |
            Review the diff for PR #${{ github.event.pull_request.number }}.
            Focus on correctness bugs, security, and missing tests. Post findings
            as a single PR review comment; do not approve or merge.
          claude_args: '--model claude-opus-4-5'

Contract: Runs on every PR open/sync/reopen and posts findings as a review comment; it never approves or merges — the human decision stays human.

Requirements

  • anthropics/claude-code-action + CLAUDE_CODE_OAUTH_TOKEN (or ANTHROPIC_API_KEY)
  • SHA-pin the action version for CI supply-chain safety

Seen in

issue-labelclaude-codegithub-actions

New issues sit un-triaged, so priority and type are unknown until a maintainer gets to them. Classify each new issue automatically and post a triage summary — as suggestions, so the maintainer keeps the final call.

Recipe · yaml
name: issue-triage
on:
  issues:
    types: [opened]
  workflow_dispatch:
    inputs:
      issue_number:
        description: 'Issue number to triage'
        required: true
        type: string

permissions:
  contents: read
  issues: write
  id-token: write

jobs:
  triage:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v4
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          prompt: |
            Triage this GitHub issue. Classify type (bug/feature/chore/docs),
            suggest labels from `gh label list`, estimate complexity and priority,
            and post a summary table as a comment.
            IMPORTANT: only SUGGEST labels — do not apply them. The maintainer decides.
          claude_args: '--max-turns 3 --model claude-haiku-4-5'

Contract: On every new issue, posts a triage table (type, labels, complexity, priority) as a comment; labels are suggested only and never auto-applied.

Requirements

  • anthropics/claude-code-action + ANTHROPIC_API_KEY
  • issues: write permission
  • Haiku keeps per-issue cost low

Seen in

scheduleclaude-codegithub-actions

Skill content goes stale as upstream frameworks release new versions, and manually re-writing docs is slow. A weekly pipeline refreshes indices, has an agent regenerate the affected skill files, validates them, and opens a PR only when there's a real change.

Recipe · yaml
name: ai-skill-maintenance
on:
  schedule:
    - cron: '17 5 * * 1'   # Mondays 05:17 UTC
  workflow_dispatch:
    inputs:
      dry_run:
        description: "Dry run (don't write files)"
        type: boolean
        default: false

permissions:
  contents: write
  pull-requests: write

jobs:
  maintain:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Refresh upstream indices
        run: node scripts/refresh-indices.mjs
      - name: Generate skill updates with the Anthropic SDK
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          AI_DRY_RUN: ${{ inputs.dry_run }}
        run: |
          npm i @anthropic-ai/sdk
          node scripts/ai-generate-updates.mjs
      - name: Validate updates before proposing them
        run: npm run eval:skills
      - name: Open PR if skills changed
        uses: peter-evans/create-pull-request@v6
        with:
          branch: skills/ai-maintenance
          title: 'chore(skills): AI-assisted maintenance update'
          commit-message: 'chore(skills): AI-assisted maintenance update'
          body: 'Automated skill refresh. Eval harness passed before this PR was opened.'
          delete-branch: true

Contract: Opens a PR only when the eval harness passes on regenerated skills; if only indices changed it falls back to an index-only PR, and a clean run produces nothing.

Requirements

  • @anthropic-ai/sdk + ANTHROPIC_API_KEY
  • An eval/validation step to gate generated content
  • peter-evans/create-pull-request

Seen in

schedulegithub-actions

Docs and specs silently fall out of sync with the live system, and you only notice when someone follows stale instructions. A weekly tripwire diffs the committed baseline against reality and files an issue on drift — detect-only, so it never rewrites your specs behind your back.

Recipe · yaml
name: weekly-doc-drift
on:
  schedule:
    - cron: '17 6 * * 1'   # Mondays 06:17 UTC
  workflow_dispatch:

permissions:
  contents: read
  issues: write

jobs:
  drift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Compare live state against the committed baseline
        id: check
        run: |
          # Exit codes form the contract: 0 fresh / 3 drift / 1 hard-fail.
          set +e
          python scripts/check_drift.py --baseline specs/baseline.json
          echo "code=$?" >> "$GITHUB_OUTPUT"
      - name: Open or update a drift issue
        if: steps.check.outputs.code == '3'
        uses: actions/github-script@v7
        with:
          script: |
            const title = 'Spec drift detected';
            const existing = (await github.rest.issues.listForRepo({
              ...context.repo, state: 'open', labels: 'drift'
            })).data[0];
            const body = 'The live catalog no longer matches specs/baseline.json. Review and reconcile.';
            if (existing) await github.rest.issues.update({ ...context.repo, issue_number: existing.number, body });
            else await github.rest.issues.create({ ...context.repo, title, body, labels: ['drift'] });
      - name: Fail loudly on a hard error
        if: steps.check.outputs.code == '1'
        run: 'echo "Drift check could not run (auth/parse error)." && exit 1'

Contract: Detect-only: never writes specs. Exit 0 = fresh (silent), 3 = drift (opens/updates a single 'drift' issue), 1 = hard failure (fails the job loudly so a silent pass can never masquerade as fresh).

Requirements

  • A drift-check script with a stable 3-state exit contract
  • issues: write permission
  • A committed baseline to diff against

Seen in

schedulegithub-actions

Stale things — untouched memory entries, dead flags, flaky tests quarantined and forgotten — accumulate with no forcing function to clean them up. A nightly sweep scans for entries past a staleness threshold and files a report.

Recipe · yaml
name: stale-entry-hunt
on:
  schedule:
    - cron: '0 4 * * *'   # 04:00 UTC daily
  workflow_dispatch:
    inputs:
      threshold_days:
        description: 'Staleness cutoff (days)'
        type: number
        default: 30

permissions:
  contents: write

jobs:
  hunt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Scan for stale entries
        run: |
          python scripts/staleness_scan.py \
            --threshold ${{ inputs.threshold_days || 30 }} \
            --out docs/reports/staleness.md
      - name: Upload the report
        uses: actions/upload-artifact@v4
        with:
          name: staleness-report
          path: docs/reports/staleness.md

Contract: Produces a staleness report artifact every night; the threshold is configurable on manual runs and defaults to 30 days.

Requirements

  • A staleness-scan script for your domain
  • actions/upload-artifact for the report

Seen in

schedulegithub-actions

Cutting a release by hand — bumping the version, writing the changelog, tagging — is forgettable toil. Let conventional commits drive it: on every push plus a weekly floor, generate the changelog and open a release PR that becomes a tagged release on merge.

Recipe · yaml
name: scheduled-changelog
on:
  push:
    branches: [main]
  schedule:
    - cron: '0 9 * * 1'   # Mondays 09:00 UTC floor
  workflow_dispatch:

permissions:
  contents: write
  pull-requests: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Run release-please
        uses: googleapis/release-please-action@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          release-type: node

Contract: release-please analyzes conventional commits, opens/updates a release PR with the computed version bump and generated CHANGELOG; merging that PR creates the GitHub Release and tag.

Requirements

  • Conventional Commits discipline in the history
  • googleapis/release-please-action
  • contents + pull-requests write permission

Seen in

schedulegithub-actions

Security scanning that only runs on PRs misses vulnerabilities introduced by dependency advisories published after a merge. A weekly CodeQL sweep plus a secret scan on every push keeps coverage continuous.

Recipe · yaml
name: security-scan-cron
on:
  push:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'   # Mondays 06:00 UTC
  workflow_dispatch:

permissions:
  contents: read
  security-events: write

jobs:
  codeql:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: github/codeql-action/init@v3
        with:
          languages: javascript-typescript
      - uses: github/codeql-action/analyze@v3

  secret-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Contract: CodeQL runs weekly (and on push) and uploads findings to the Security tab; Gitleaks scans full history for committed secrets. Findings surface as alerts rather than blocking the push.

Requirements

  • security-events: write permission for CodeQL upload
  • github/codeql-action and gitleaks/gitleaks-action

Seen in

schedulegithub-actions

Inactive issues pile up and hide the ones that matter, but closing them by hand is thankless. A daily sweep marks issues stale after inactivity and closes them if nothing changes, while exempting the labels you care about.

Recipe · yaml
name: stale-issue-sweep
on:
  schedule:
    - cron: '30 1 * * *'   # 01:30 UTC daily
  workflow_dispatch:

permissions:
  issues: write
  pull-requests: write

jobs:
  stale:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/stale@v9
        with:
          days-before-stale: 30
          days-before-close: 7
          stale-issue-label: stale
          exempt-issue-labels: 'good first issue,help wanted,security,pinned'
          stale-issue-message: 'This issue is stale after 30 days of inactivity. It will close in 7 days without further activity.'
          close-issue-message: 'Closing as stale. Comment to reopen.'
          days-before-pr-stale: -1   # leave PRs alone

Contract: Marks an issue stale after 30 days idle and closes it 7 days later unless it carries an exempt label or gets new activity; PRs are untouched.

Requirements

  • actions/stale
  • issues: write permission
  • An agreed exempt-label set

Seen in

schedulegithub-actions

A generated README (catalog, index, badge table) drifts from the underlying data between manual regenerations. A scheduled job rebuilds it from source and commits only when the output actually changed.

Recipe · yaml
name: scheduled-readme-refresh
on:
  schedule:
    - cron: '0 * * * *'   # hourly
  workflow_dispatch:

permissions:
  contents: write

jobs:
  refresh:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Regenerate README from source data
        run: node scripts/generate-readme.mjs
      - name: Commit only if it changed
        run: |
          if ! git diff --quiet README.md; then
            git config user.name 'github-actions[bot]'
            git config user.email 'github-actions[bot]@users.noreply.github.com'
            git add README.md
            git commit -m 'docs(readme): scheduled refresh [skip ci]'
            git push
          fi

Contract: Rebuilds README.md from source on schedule and commits only on a real diff; an unchanged README yields no commit.

Requirements

  • A README-generation script
  • contents: write permission

Seen in

scheduleclaude-codegithub-actions

A dependency you track (an upstream CLI, a framework) ships changes you need to adopt, but nobody watches its changelog daily. A watcher diffs the upstream changelog, and — optionally with an agent — files adoption issues for the gaps worth acting on.

Recipe · yaml
name: upstream-release-watch
on:
  schedule:
    - cron: '0 3 * * *'   # 03:00 UTC daily
  workflow_dispatch:

permissions:
  contents: read
  issues: write

jobs:
  watch:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Fetch and diff the upstream changelog
        id: diff
        run: |
          curl -sL https://raw.githubusercontent.com/UPSTREAM/OWNER/main/CHANGELOG.md -o /tmp/upstream.md
          if ! diff -q .cache/upstream-changelog.md /tmp/upstream.md >/dev/null 2>&1; then
            cp /tmp/upstream.md .cache/upstream-changelog.md
            echo 'changed=true' >> "$GITHUB_OUTPUT"
          fi
      - name: Optional LLM triage of the new entries
        if: steps.diff.outputs.changed == 'true' && env.CLAUDE_CODE_OAUTH_TOKEN != ''
        env:
          CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
        run: npx @anthropic-ai/claude-code -p --bare 'Summarize the new upstream changelog entries and list which ones require adoption work here.' < /tmp/upstream.md
      - name: File a manual-triage issue as a fallback
        if: steps.diff.outputs.changed == 'true'
        uses: actions/github-script@v7
        with:
          script: |
            await github.rest.issues.create({ ...context.repo,
              title: 'Upstream changelog changed — review for adoption',
              body: 'New upstream entries detected. Review and open adoption tasks as needed.',
              labels: ['upstream-adoption'] });

Contract: Only acts when the upstream changelog changed. LLM triage is a soft dependency (skipped cleanly if the token is absent); either way a fallback 'upstream-adoption' issue is filed so a change is never silently dropped.

Requirements

  • A cached copy of the upstream changelog to diff against
  • Optional CLAUDE_CODE_OAUTH_TOKEN for the LLM triage step
  • issues: write permission

Seen in

schedulegithub-actions

You mean to cut a package release 'soon' but lose track of whether it's actually due — commits accrued, version proposed, npm behind the tag. A daily check computes readiness and upserts a single tracking issue instead of spamming new ones.

Recipe · yaml
name: release-readiness-watch
on:
  schedule:
    - cron: '15 16 * * *'   # 16:15 UTC daily
  workflow_dispatch:

permissions:
  contents: read
  issues: write

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Compute release readiness
        id: ready
        run: node scripts/check-release-due.mjs >> "$GITHUB_STEP_SUMMARY"
      - name: Upsert a single tracking issue
        uses: actions/github-script@v7
        with:
          script: |
            const title = 'Release readiness';
            const open = (await github.rest.issues.listForRepo({
              ...context.repo, state: 'open', labels: 'release-watch'
            })).data[0];
            const body = 'See the latest workflow run summary for commit count, proposed version, and npm status.';
            if (open) await github.rest.issues.update({ ...context.repo, issue_number: open.number, body });
            else await github.rest.issues.create({ ...context.repo, title, body, labels: ['release-watch'] });

Contract: Writes a readiness summary to the run each day and keeps exactly one open 'release-watch' issue (upsert), so the signal never becomes issue spam.

Requirements

  • A readiness-check script (commit count, version, registry state)
  • issues: write permission

Seen in

schedulegithub-actions

A content repo (registry entries, schemas, generated site) can pass per-PR checks yet still rot in aggregate. A weekly full validation re-runs every schema, policy, and build lane so drift is caught even when no one touched the files.

Recipe · yaml
name: content-validation-sweep
on:
  pull_request:
  schedule:
    - cron: '17 9 * * 1'   # Mondays 09:17 UTC full sweep
  workflow_dispatch:

permissions:
  contents: read

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - name: Schema + policy validation
        run: npm run validate:content
      - name: Build the generated site
        run: npm run build
      - name: Validate registry artifacts
        run: npm run validate:registry

Contract: Runs on PRs for changed lanes and does a full weekly sweep of schema, policy, build, and registry validation so aggregate drift surfaces even without a triggering change.

Requirements

  • Validation scripts for each content lane
  • A reproducible build step

Seen in

pr-opengithub-actions

When a spec is the source of truth, small edits can quietly violate invariants — a rubric weight that no longer sums, a version marker left un-bumped, a frontmatter field dropped. A push/PR guard runs the whole battery of consistency assertions so a broken spec never merges.

Recipe · yaml
name: spec-consistency-guard
on:
  push:
  pull_request:

permissions:
  contents: read

jobs:
  consistency:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Run the spec-consistency assertions
        run: |
          # Each script asserts one invariant and exits non-zero on violation,
          # so any broken assertion fails the whole job.
          python scripts/validate_schema.py
          python scripts/check_version_consistency.py
          python scripts/check_rubric_weights.py
          python scripts/check_frontmatter.py

Contract: Every invariant script must exit zero for the job to pass; a single violated assertion (schema, version, rubric, frontmatter) blocks the merge.

Requirements

  • A suite of single-invariant validation scripts
  • Runs on push and pull_request

Seen in

03FAQ

Agent Automation FAQ

What is an agent automation recipe?

A recipe is a complete, runnable workflow that starts an agent (or an agent-adjacent job) without a human driving each turn — a nightly dependency sweep, a CI-failure repair, a PR review on open. Each one is a trigger plus a contract: what wakes it, what it does, and what it guarantees on success and failure.

How do automations relate to loops?

An automation is the trigger-and-schedule layer of a loop. Loop engineering is the broader discipline of designing systems that prompt, verify, retry, and stop an agent; the automation is what fires the loop — a cron entry, a workflow_run on CI failure, an issue label. The recipes here map to the loop shapes described in our Loop Engineering guide.

Do these recipes require an AI agent to run?

Some do and some don't. Recipes tagged with a claude-code runtime invoke an agent via anthropics/claude-code-action or the Anthropic SDK (CI-failure repair, issue triage, PR review). Others are pure GitHub Actions scaffolds — dependency sweeps, changelog automation, security scans — that form the deterministic backbone a loop runs inside. Both are useful; the AI-backed ones need an API key or OAuth token.

What is the 'contract' listed on each recipe?

The contract is the guarantee the automation makes — for example, 'a PR is opened only if tests pass' or 'detect-only: never writes specs; files an issue on drift.' A good automation has an explicit contract so its behavior on both the happy path and the failure path is predictable, which is exactly what makes it safe to leave running unattended.

How do I keep an automated loop from running away?

Bound it. Every AI-backed recipe here uses guards: a max-turns cap so the agent can't thrash, a branch-name check so a repair run can't re-trigger itself, and human checkpoints (PRs, suggested-only labels) at anything irreversible. The scheduled recipes commit or open PRs only on a real diff, so a no-op run produces no noise.

04Next

Keep Going

Stay Updated with Claude Skills

Subscribe to get the latest Claude Skills, tutorials, and community highlights delivered to your inbox.

We respect your privacy. Unsubscribe at any time.