Reusable GitHub Actions and workflows for Claude-powered automation — PR review, @claude mention responses, lint-failure diagnosis, CI-failure diagnosis, and manual fix application — built on top of anthropics/claude-code-action.
CLAUDE_CODE_OAUTH_TOKEN is required for all actions. All actions also require APP_ID and APP_PRIVATE_KEY — credentials for a GitHub App that issues short-lived tokens at job time, ensuring a consistent bot identity (e.g., my-app[bot]) across all Claude-powered comments and commits.
New consumer? Start with docs/consumer-onboarding.md — an end-to-end walkthrough for wiring this library into a glitchwerks org repo for the first time. The reference docs below assume you have already completed the basic setup it covers.
| Action | Description | Usage pattern |
|---|---|---|
pr-review |
Claude reviews a PR for code quality, security, performance, test coverage, and docs | Composite action or reusable workflow |
tag-claude |
Claude responds to @claude mentions in issue and PR comments |
Composite action or reusable workflow |
lint-failure |
Claude diagnoses lint failures on a PR and optionally commits a fix | Composite action or reusable workflow |
apply-fix |
Validates and applies a unified diff to a PR branch (manual or via auto-fix) | Composite action or reusable workflow |
Critical: GitHub ignores
permissions:declared at the job level when a job calls a reusable workflow (uses:). You must declare permissions at the workflow level (top-levelpermissions:key, outside ofjobs:). Job-level permissions are silently ignored in this context, which can cause cryptic 403 errors or missing write access at runtime.
Container pull permission: All Phase 5 reusable workflows run inside container-pinned overlay images pulled from
ghcr.io/glitchwerks/claude-runtime-{review,fix,explain}. The runner does an implicitdocker pullbefore the job's first step executes, authenticated withGITHUB_TOKEN.packages: readmust be present in the workflow-level permissions block — without it the pull fails withmanifest unknown(an authorization-masked error that looks like the image does not exist). See issue #192 for the original diagnosis.
The table below shows the minimum required permissions for each consumer workflow file. Copy the exact block shown into the top level of your workflow (after on:, before jobs:).
| Workflow | Trigger | Required permissions: block |
|---|---|---|
| PR Review | pull_request |
contents: readpull-requests: writepackages: read |
| Tag Claude | issue_comment + pull_request_review_comment |
contents: writeissues: writepull-requests: writepackages: read |
| Claude Lint Failure | pull_request (via needs: [lint], if: failure()) |
contents: writepull-requests: writeactions: readpackages: read |
| CI Failure Diagnosis | workflow_run |
contents: writepull-requests: writepackages: read |
| Apply Fix | workflow_dispatch |
contents: writepull-requests: writepackages: read |
The easiest way to consume these actions is via the reusable workflow pattern. Add one file to your repo and you're done.
# .github/workflows/pr-review.yml
name: PR Review
on:
pull_request:
types: [opened, synchronize, reopened]
# Permissions must be declared at workflow level (not job level) when calling
# reusable workflows. pull_request events default to pull-requests: none.
permissions:
contents: read
pull-requests: write
packages: read
jobs:
review:
uses: glitchwerks/github-actions/.github/workflows/claude-pr-review.yml@v2
secrets:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
app_id: ${{ secrets.APP_ID }}
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}Optional inputs:
with:
model: claude-opus-4-5 # default: claude-sonnet-4-5
max_turns: '20' # default: 15# .github/workflows/tag-claude.yml
name: Tag Claude
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
# Permissions must be declared at workflow level when calling reusable workflows.
# contents: write is safe here — issue_comment always runs in the base repo context.
permissions:
contents: write
issues: write
pull-requests: write
packages: read
jobs:
respond:
uses: glitchwerks/github-actions/.github/workflows/claude-tag-respond.yml@v2
secrets:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
app_id: ${{ secrets.APP_ID }}
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}Optional inputs:
with:
trigger_phrase: '@bot' # default: @claude
require_association: false # default: true — set false to allow all commenters
authorized_users: 'alice,bob' # default: '' — when set, only these users can trigger Claude| Secret | Purpose |
|---|---|
CLAUDE_CODE_OAUTH_TOKEN |
Authenticates claude-code-action |
APP_ID |
(Required) GitHub App ID — used to generate a short-lived token for git push and API calls |
APP_PRIVATE_KEY |
(Required) GitHub App private key (PEM format) |
By default, only commenters with an author_association of OWNER, MEMBER, or COLLABORATOR can trigger Claude. This prevents arbitrary GitHub users from consuming your Claude quota.
| Scenario | Configuration |
|---|---|
| Default — org members and collaborators only | (no extra config needed) |
| Open to all commenters | require_association: false |
| Explicit allowlist (overrides association check) | authorized_users: 'alice,bob' |
require_association (boolean, default true) — when true, only OWNER, MEMBER, and COLLABORATOR associations are allowed. Set to false to let anyone trigger Claude regardless of their relationship to the repository.
authorized_users (string, default '') — comma-separated list of GitHub usernames (case-insensitive). When non-empty, only the listed users can trigger Claude and the require_association check is skipped entirely.
Concurrency — the reusable workflow enforces per-user concurrency automatically. If the same user triggers Claude a second time while a run is already in progress, the in-progress run is cancelled and the new one proceeds. This prevents queued pile-ups from rapid @claude mentions. When using the composite action directly, add the equivalent concurrency block to your job:
jobs:
respond:
concurrency:
group: claude-tag-${{ github.repository }}-${{ github.event.comment.user.login }}
cancel-in-progress: trueIf you need more control (e.g., embed the review step inside a larger job), use the composite actions directly instead of the reusable workflows.
pr-review/— composite action docs and examplestag-claude/— composite action docs and examples
- All
uses:references must change from@v1to@v2. Every workflow or composite action call that pins to@v1must be updated. gh_patinput has been removed. The deprecatedgh_patinput is no longer accepted by any action.app_id+app_private_keyare now required for all actions (tag-claude,lint-failure,ci-failure,apply-fix,pr-review).tag-claudeno longer falls back togithub.token. An App token is mandatory — omittingapp_idandapp_private_keywill cause the action to fail at the token resolution step.- All actions now post under the GitHub App's bot identity (e.g.,
my-app[bot]) rather than undergithub-actions[bot]or a PAT's user identity. This includespr-review, which previously usedgithub.token(issue #250).
- Update every
uses: glitchwerks/github-actions/...@v1line (anduses: .github/workflows/...@v1) to@v2. - If you were passing
gh_pat, create a GitHub App and addAPP_ID+APP_PRIVATE_KEYas repository secrets. See the GitHub App setup section for instructions. - For
tag-claudeandpr-reviewconsumers: ensure your caller workflow passesapp_idandapp_private_keysecrets.
v1 — using the deprecated gh_pat input:
jobs:
respond:
uses: glitchwerks/github-actions/.github/workflows/claude-tag-respond.yml@v1
secrets:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
gh_pat: ${{ secrets.GH_PAT }}v2 — using App token:
jobs:
respond:
uses: glitchwerks/github-actions/.github/workflows/claude-tag-respond.yml@v2
secrets:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
app_id: ${{ secrets.APP_ID }}
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}| Ref | Meaning |
|---|---|
@v2 |
Stable floating tag — points to the latest v2.x.x release. Use this in production. |
@v2.0.0 |
Pinned tag for reproducible builds. |
@v1 / @v1.8.0 |
Still available for consumers who have not yet migrated. No further updates. |
@main |
Latest development commit. May include breaking changes. |
When a new major version is released (e.g., v3), a new floating tag will be created. The @v2 tag will continue to point to the last v2.x.x release for backwards compatibility.
runtime/base/Dockerfile produces the shared foundation image consumed by every Claude-powered CI overlay. Built and pushed by .github/workflows/runtime-build.yml STAGE 2 to ghcr.io/glitchwerks/claude-runtime-base. Smoke-tested by STAGE 4-base as a non-root UID. See docs/superpowers/specs/2026-04-21-ci-claude-runtime-design.md §3 for architecture and docs/superpowers/plans/phase-2-base-image.md for the implementation plan.
runtime/overlays/{review,fix,explain}/ produce the three verb-scoped overlay images consumed by Phase 5's reusable workflows. Each overlay is built FROM ghcr.io/glitchwerks/claude-runtime-base@sha256:<digest> and adds verb-specific agents/plugins per runtime/ci-manifest.yaml. Built and pushed by .github/workflows/runtime-build.yml STAGE 3 (parallel matrix; one cell per verb); smoke-tested by STAGE 4-overlay against per-overlay expected.yaml inventory contracts. The matcher (runtime/scripts/inventory-match.sh) enforces "different eyes" guarantees mechanically — a future edit that imports code-writer into review fails the build.
The overlay layer also introduces overlays.<verb>.subtract_from_shared.plugins, which removes a base-inherited plugin from a specific overlay at build time (review subtracts skill-creator for §10.2 compliance). See spec §4.2 / §5.1 amendments for the relationship to merge_policy.overrides (no interaction). Implementation plan: docs/superpowers/plans/phase-3-overlays.md.
The Phase 3 overlay images are wired into the consumer-facing reusable workflows via container: ghcr.io/.../claude-runtime-<verb>@sha256:<digest> at the job level. Every step in the job — including the embedded claude-code-action@v1 invocation — runs inside the verb-specific overlay image, which bakes Claude CLI at $PATH_TO_CLAUDE_CODE_EXECUTABLE plus the verb's agent set. The interface to consumers is unchanged: a single uses: line plus the OAuth secret. See docs/superpowers/specs/2026-04-21-ci-claude-runtime-design.md §7.5 for workflow → overlay assignments and docs/superpowers/plans/2026-04-22-ci-claude-runtime.md "Phase 5" for the implementation plan.
For the claude-tag-respond.yml flow, the route job invokes claude-command-router/ (Phase 4) to parse the verb and emits a digest-pinned image URL that the dispatch job consumes via container: ${{ needs.route.outputs.image }}. Consumers don't see the routing — they just uses: the workflow as before.
Migration note (Phase 5 → Phase 7): claude-lint-fix.yml, apply-fix.yml, and ci-failure.yaml are kept until Phase 7 cutover. New consumers should reference claude-lint-failure.yml, claude-apply-fix.yml, and claude-ci-failure.yml respectively — all three are container-pinned to the fix overlay.
The Phase 5 reusable workflows pull overlay images from ghcr.io/glitchwerks/claude-runtime-{review,fix,explain} at job startup. Consumer caller workflows must be able to pull these images. There are two patterns depending on where the consumer repo lives.
Set each package's visibility to Internal via:
https://github.com/orgs/<org>/packages/container/<package-name>/settings
Select "Change package visibility" → "Internal". This grants every repository owned by the glitchwerks org read access to the package using the ambient GITHUB_TOKEN — no per-repo bookkeeping needed. Repeat for all three packages: claude-runtime-review, claude-runtime-fix, claude-runtime-explain.
Use the per-repo grant: package settings → "Manage Actions access" → add the consumer repo with role Read. This must be done for each of the three packages.
Regardless of visibility setting, the consumer's caller workflow must declare packages: read at the workflow level:
permissions:
contents: read # or write, as required
pull-requests: write # as required
packages: read # required for GHCR image pullThe visibility setting is a necessary condition (it allows the pull in principle), but packages: read in the workflow permissions is also necessary (it authorizes the implicit docker pull that runs before any step). An implicit docker login step in the workflow body cannot substitute for the missing permission — the pull happens before any step runs. Omitting packages: read results in a manifest unknown error that looks like the image is absent rather than forbidden. See issue #192 for the original diagnosis.
The ci-failure workflow watches for failed runs of a workflow named CI and automatically diagnoses the failure using Claude. When confidence is high it can also apply the fix directly to the PR branch — no manual intervention needed.
- The
workflow_runtrigger fires whenever aCIworkflow completes with afailureconclusion. - A bash step resolves the PR number from the failing commit SHA.
gh run view --log-failedfetches plain-text logs for failed steps only (up to 16 000 chars) into/tmp/ci_logs.txt.- The PR diff is fetched from the GitHub API into
/tmp/pr_diff.json. anthropics/claude-code-action@v1runs Claude, which reads both files, posts a structured diagnosis comment on the PR, and — whenauto_applyistrueand confidence ishigh— applies the fix, commits it, and pushes to the PR branch in the same turn.
| Secret | Purpose |
|---|---|
CLAUDE_CODE_OAUTH_TOKEN |
Authenticates claude-code-action |
APP_ID |
GitHub App ID — used to generate a short-lived token for git push and API calls |
APP_PRIVATE_KEY |
GitHub App private key |
- Create a GitHub App (Settings → Developer settings → GitHub Apps → New GitHub App).
- Grant it Contents: read and write and Pull requests: read and write permissions.
- Install the App on your repository.
- Note the App ID from the App's settings page.
- Generate a private key (PEM format) from the App's settings page.
- Add
APP_IDandAPP_PRIVATE_KEYas repository secrets.
| Input | Type | Default | Description |
|---|---|---|---|
model |
string | claude-sonnet-4-5 |
Claude model to use |
max_turns |
string | 15 |
Maximum Claude turns per run |
auto_apply |
boolean | true |
When true, Claude applies a high-confidence fix automatically |
# .github/workflows/ci-failure.yml
name: CI Failure Diagnosis
on:
workflow_run:
workflows: ["CI"]
types: [completed]
permissions:
contents: write
pull-requests: write
packages: read
jobs:
diagnose:
uses: glitchwerks/github-actions/.github/workflows/claude-ci-failure.yml@v2
secrets:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
app_id: ${{ secrets.APP_ID }}
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}Optional inputs:
with:
model: claude-opus-4-5 # default: claude-sonnet-4-5
max_turns: '20' # default: 15
auto_apply: false # default: true — set false to diagnose onlyThe claude-lint-fix workflow lets consumers drop a single notify-claude job into their existing lint workflow. When linting fails on a PR, Claude fetches the failed step logs and the PR diff, posts a structured ## Claude Lint Diagnosis comment, and — when auto_apply is true — commits a high-confidence fix directly to the PR branch.
- The consumer's
notify-claudejob depends on theirlintjob (needs: [lint]) and runs only on failure (if: failure()). gh run view --log-failedfetches plain-text logs for failed lint steps only (up to 16 000 chars) into/tmp/lint_logs.txt.- The PR diff is fetched from the GitHub API into
/tmp/pr_diff.json. anthropics/claude-code-action@v1runs Claude, which reads both files, posts a structured diagnosis comment on the PR, and — whenauto_applyistrueand confidence ishigh— applies the fix, commits it, and pushes to the PR branch in the same turn.
# .github/workflows/lint.yml
name: Lint
on:
pull_request:
types: [opened, synchronize, reopened]
# Permissions must be declared at workflow level (not job level) when calling
# reusable workflows. actions: read is required to fetch failed run logs.
permissions:
contents: write
pull-requests: write
actions: read
packages: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run lint # or whatever linter the repo uses
notify-claude:
needs: [lint]
if: failure()
uses: glitchwerks/github-actions/.github/workflows/claude-lint-failure.yml@v2
with:
pr_number: ${{ github.event.pull_request.number }}
run_id: ${{ github.run_id }}
# auto_apply: true # opt-in to auto-fix
secrets:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
app_id: ${{ secrets.APP_ID }}
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}| Secret | Purpose |
|---|---|
CLAUDE_CODE_OAUTH_TOKEN |
Authenticates claude-code-action |
APP_ID |
GitHub App ID — used to generate a short-lived token for git push and API calls |
APP_PRIVATE_KEY |
GitHub App private key |
| Input | Type | Required | Default | Description |
|---|---|---|---|---|
pr_number |
string | yes | — | Pull request number — pass ${{ github.event.pull_request.number }} |
run_id |
string | yes | — | Caller's workflow run ID — pass ${{ github.run_id }} |
model |
string | no | claude-sonnet-4-5 |
Claude model to use |
max_turns |
string | no | 10 |
Maximum Claude turns per run |
auto_apply |
boolean | no | false |
When true, Claude applies a high-confidence fix automatically |
The apply-fix workflow (and its backing composite action at apply-fix/) checks out a PR branch, validates a unified diff against protected paths, applies it, commits, and pushes. It is invoked automatically by the CI Failure Diagnosis workflow when auto_apply is true and confidence is high, but can also be triggered manually.
| Secret | Purpose |
|---|---|
APP_ID |
GitHub App ID — used to generate a short-lived token for git push |
APP_PRIVATE_KEY |
GitHub App private key |
| Input | Required | Description |
|---|---|---|
pr_number |
Yes | The PR number to apply the fix to |
fix_diff |
Yes | A unified diff string (output of git diff or similar) |
fix_description |
Yes | One-line description used as the commit message |
Protected paths — the workflow will fail with a clear error if the diff targets any file under
.github/. This prevents automated changes to workflow files.
# Trigger via GitHub UI or gh CLI:
gh workflow run claude-apply-fix.yml \
-f pr_number=42 \
-f fix_description="Fix missing null check in auth handler" \
-f fix_diff="$(cat my.patch)"The logic is encapsulated in apply-fix/action.yml so it can be embedded directly in a larger job without spawning a separate workflow:
- uses: glitchwerks/github-actions/apply-fix@v2
with:
pr_number: '42'
fix_diff: ${{ steps.diagnosis.outputs.fix_diff }}
fix_description: 'Fix missing null check'
app_id: ${{ secrets.APP_ID }}
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}Q: What happens if I don't provide any token?
The action will fail with a clear ::error:: message at the token resolution step rather than with a cryptic authentication failure downstream. APP_ID + APP_PRIVATE_KEY are required for all write-capable actions.
Q: What happens when App token generation fails?
If actions/create-github-app-token fails (e.g. wrong App ID, malformed private key), the "Resolve write token" step will immediately fail with an explicit error message directing you to the README. Check that APP_ID and APP_PRIVATE_KEY are correctly configured.
All pull requests are linted automatically with actionlint, which validates workflow syntax, expression types, and shell scripts. Run it locally before pushing:
brew install actionlint # macOS
actionlint # from repo root- A
CLAUDE_CODE_OAUTH_TOKENsecret must be set on the consuming repository (or organization). Obtain this token from claude.ai. - A GitHub App (
APP_ID+APP_PRIVATE_KEY) is required for all actions — for identity consistency (pr-review) and write operations (git push, triggering downstream workflows) in the others. See the GitHub App setup section under CI Failure Diagnosis for instructions.