Expert guidance for creating and maintaining GitHub Actions workflows and reusable workflows with security best practices
/plugin marketplace add teliha/dev-workflows/plugin install dev-workflows@dev-workflowsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
README.mdThis skill automatically activates when:
.github/workflows/*.yml filesaction.yml files (composite actions)Decision Tree: What Type of Workflow Should I Create?
Is build/test tooling required?
├─ NO → Create Universal Reusable Workflow
│ └─ Works for ALL project types
│ └─ Example: security-audit.yml, code-review.yml
│
└─ YES → Create Composite Action
└─ Caller provides build environment
└─ Example: fix-ci, improve-coverage
Universal Reusable Workflows (Analysis Tasks)
Composite Actions (Build/Test Tasks)
❌ Don't create project-specific variations of the same workflow
# BAD: Multiple similar workflows
fix-ci-foundry.yml
fix-ci-nodejs.yml
fix-ci-python.yml
✅ Instead: Create one composite action, let caller setup environment
# GOOD: One universal action
.github/actions/fix-ci/action.yml # Used by all project types
CRITICAL: You MUST read the entire workflow file before making recommendations.
Identify what type of workflow this is:
workflow_call trigger)action.yml in .github/actions/)Principle of Least Privilege: Only grant permissions that are actually needed.
# ✅ GOOD: Minimal required permissions
permissions:
contents: read # Only if reading code
issues: write # Only if creating/updating issues
pull-requests: write # Only if commenting on PRs
id-token: write # Only if using OIDC
# ❌ BAD: Over-permissive
permissions: write-all
Check for:
secrets: section (not inputs:)?GITHUB_TOKEN used instead of PAT where possible?# ✅ GOOD: Secrets are secrets
secrets:
CLAUDE_CODE_OAUTH_TOKEN:
required: true
GH_PAT:
required: false
# ❌ BAD: Secrets as inputs (logged in plain text!)
inputs:
api_token:
required: true
GITHUB_TOKEN vs Personal Access Token (PAT):
# ✅ GOOD: Use GITHUB_TOKEN when possible (explicit form)
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
# ⚠️ USE PAT ONLY WHEN NEEDED: For submodules or cross-repo access
- uses: actions/checkout@v4
with:
token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
submodules: recursive
Note on token syntax:
${{ secrets.GITHUB_TOKEN }} - Explicit, recommended for security clarity${{ github.token }} - Context reference (works but less explicit)secrets.GITHUB_TOKEN is the official best practiceWhen to use PAT:
Required fields for each input:
inputs:
parameter_name:
description: "Clear description of what this does" # REQUIRED
required: true/false # REQUIRED
type: string/number/boolean # REQUIRED
default: "value" # Optional
Common Issues:
description fieldstype specifications# ✅ GOOD: Well-defined inputs
inputs:
node_version:
description: "Node.js version to use"
required: false
type: string
default: "20"
create_issue:
description: "Create GitHub issue on failure"
required: false
type: boolean
default: true
# ❌ BAD: Poorly defined inputs
inputs:
version:
required: false # Missing description and type!
For composite actions, inputs work differently:
# In action.yml
inputs:
failed_run_id:
description: "ID of the failed workflow run"
required: true
github_token:
description: "GitHub token for API access"
required: true # Must be passed from caller
max_attempts:
description: "Maximum fix attempts"
required: false
default: "3"
# Access via ${{ inputs.parameter_name }}
runs:
using: "composite"
steps:
- run: echo "Failed run: ${{ inputs.failed_run_id }}"
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.github_token }}
IMPORTANT: Composite actions cannot use ${{ github.token }} or ${{ secrets.* }} directly.
default: ${{ github.token }} - Does NOT work in composite actions${{ inputs.github_token }} inside the composite actionCheck for:
runs-on value (ubuntu-latest for universal workflows)needs: dependencies between jobstimeout-minutes (default 360min often too long)if: where needed# ✅ GOOD: Explicit timeout and conditions
jobs:
audit:
runs-on: ubuntu-latest
timeout-minutes: 30
if: github.event_name == 'pull_request'
Action Version Pinning:
# ✅ GOOD: Pin to major version (gets patches)
- uses: actions/checkout@v4
# ✅ BETTER: Pin to exact SHA for security-critical workflows
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
# ❌ BAD: No version specified
- uses: actions/checkout
Step Naming:
# ✅ GOOD: Clear, descriptive names
- name: Install Foundry toolchain
uses: foundry-rs/foundry-toolchain@v1
- name: Run security audit with Claude
uses: anthropics/claude-code-action@v1
# ❌ BAD: Generic or missing names
- uses: foundry-rs/foundry-toolchain@v1 # No name
- name: Run action # Too generic
uses: anthropics/claude-code-action@v1
- name: Run Claude Code Action
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # REQUIRED
prompt: | # REQUIRED
Your detailed prompt here
claude_args: '--allowed-tools "..."' # Optional but recommended
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For gh CLI
Security Best Practice: Restrict bash commands to only what's needed.
# ✅ GOOD: Minimal tool access for audit workflow
claude_args: '--allowed-tools "Bash(gh issue create:*),Bash(gh issue list:*)"'
# ✅ GOOD: Read-only access for spec checking
claude_args: '--allowed-tools "Read,Write,Glob,Grep,Bash(gh:*),Bash(find:*),Bash(cat:*),Bash(date:*)"'
# ✅ GOOD: PR review access
claude_args: '--allowed-tools "Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"'
# ❌ BAD: No restrictions (Claude can run arbitrary bash)
# (omitting claude_args entirely)
Tool restriction patterns:
Bash(gh:*) - All gh CLI commandsBash(gh issue create:*) - Only creating issuesBash(git:*) - All git commandsRead,Write,Glob,Grep - File operations onlymcp__github_inline_comment__create_inline_comment - Inline PR commentsEffective prompts include:
/audit, /fix-ci, etc.# ✅ GOOD: Comprehensive prompt
prompt: |
/audit
Please perform a comprehensive security audit.
Generate a detailed audit report and save it to `audit-report-$(date +%Y%m%d).md`.
After completing the audit:
1. If you find any CRITICAL severity issues, create a GitHub issue using the `gh` CLI
2. Include link to this workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
3. Label with: security, critical, audit
# ❌ BAD: Vague prompt
prompt: "Please audit the code"
# ✅ GOOD: Upload artifacts even on failure
- name: Upload audit report
if: always() # Run even if previous steps failed
uses: actions/upload-artifact@v4
# ✅ GOOD: Only run on success
- name: Post success comment
if: success()
run: gh pr comment ${{ inputs.pr_number }} --body "✅ Audit passed!"
# ✅ GOOD: Only run on failure
- name: Create failure issue
if: failure()
run: gh issue create --title "Workflow failed" --label bug
Best practices:
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: audit-report-${{ github.run_number }} # Unique name with run number
path: | # Multiple paths supported
audit-report-*.md
audits/**/*.md
retention-days: 90 # Don't keep forever
if-no-files-found: warn # Don't fail if missing
# ✅ GOOD: Cache dependencies
- name: Cache Node modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# ✅ GOOD: Cache Foundry installation
- name: Cache Foundry
uses: actions/cache@v4
with:
path: ~/.foundry
key: ${{ runner.os }}-foundry-${{ hashFiles('foundry.toml') }}
# ✅ GOOD: Cancel in-progress runs for PR updates
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# ⚠️ USE CAREFULLY: For deployments, don't cancel
concurrency:
group: production-deploy
cancel-in-progress: false
Use case: Security audits, code reviews, spec checking
name: Security Audit
on:
workflow_call:
secrets:
CLAUDE_CODE_OAUTH_TOKEN:
required: true
permissions:
contents: read
issues: write
id-token: write
jobs:
audit:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.GH_PAT || github.token }}
- name: Run Claude Code Audit
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: '--allowed-tools "Bash(gh issue create:*),Bash(gh issue list:*)"'
prompt: |
/audit
[Detailed instructions...]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload audit report
if: always()
uses: actions/upload-artifact@v4
with:
name: audit-report-${{ github.run_number }}
path: audit-report-*.md
retention-days: 90
Use case: CI fixes, coverage improvement
# .github/actions/fix-ci/action.yml
name: 'Fix CI Failures'
description: 'Automatically fix CI failures using Claude Code'
inputs:
failed_run_id:
description: 'ID of the failed workflow run'
required: true
claude_code_oauth_token:
description: 'Claude Code OAuth token'
required: true
runs:
using: "composite"
steps:
- name: Run Claude Code to fix CI
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ inputs.claude_code_oauth_token }}
prompt: |
/fix-ci
Failed run ID: ${{ inputs.failed_run_id }}
[Instructions for fixing CI...]
shell: bash
Caller workflow (Foundry project):
name: Auto-fix CI (Foundry)
on:
workflow_run:
workflows: ["Tests"]
types: [completed]
permissions:
contents: write
pull-requests: write
jobs:
fix-ci:
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Caller provides Foundry setup
- uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
- run: forge install
# Universal action runs in this environment
- uses: ./.github/actions/fix-ci
with:
failed_run_id: ${{ github.event.workflow_run.id }}
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }} # Must pass explicitly
name: Code Review
on:
workflow_call:
inputs:
pr_number:
description: "Pull request number"
required: true
type: string
secrets:
CLAUDE_CODE_OAUTH_TOKEN:
required: true
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Review PR
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: '--allowed-tools "Bash(gh pr:*),mcp__github_inline_comment__create_inline_comment"'
prompt: |
/code-review
PR #${{ inputs.pr_number }}
Repository: ${{ github.repository }}
Review the changes and post inline comments using gh CLI or MCP tools.
When reviewing a workflow, systematically check:
secrets: (not inputs:)claude_args restricts bash tool accessGITHUB_TOKEN used instead of PAT where possibledescription, required, typeif: always() for artifact uploads# BAD: Conditional setup based on project detection
- name: Detect project type
id: detect
run: |
if [ -f "foundry.toml" ]; then
echo "type=foundry" >> $GITHUB_OUTPUT
elif [ -f "package.json" ]; then
echo "type=nodejs" >> $GITHUB_OUTPUT
fi
- name: Setup Foundry
if: steps.detect.outputs.type == 'foundry'
uses: foundry-rs/foundry-toolchain@v1
- name: Setup Node
if: steps.detect.outputs.type == 'nodejs'
uses: actions/setup-node@v4
Why it's bad:
Better approach:
# BAD: Hardcoded versions and paths
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18 # Hardcoded!
- run: npm ci
working-directory: ./frontend # Hardcoded path!
Better:
# GOOD: Parameterized
inputs:
node_version:
description: "Node.js version"
type: string
default: "20"
working_directory:
description: "Working directory"
type: string
default: "."
# Use in steps
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node_version }}
- run: npm ci
working-directory: ${{ inputs.working_directory }}
# BAD: Too many permissions
permissions: write-all
# BAD: Unrestricted bash access
- uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# No claude_args = unrestricted bash
Better:
# GOOD: Minimal permissions
permissions:
contents: read
issues: write
# GOOD: Restricted tool access
- uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: '--allowed-tools "Bash(gh issue create:*)"'
# BAD: No artifact upload on failure
- name: Run tests
run: npm test
- name: Upload coverage # Never runs if tests fail!
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
Better:
# GOOD: Always upload artifacts
- name: Run tests
run: npm test
continue-on-error: true # Or remove this to fail workflow
- name: Upload coverage
if: always() # Always run, even on failure
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
if-no-files-found: warn
Issue: "Resource not accessible by integration"
permissions:Issue: "Secret not found"
Issue: Claude Code Action timeout
timeout-minutes, break into smaller tasksIssue: Artifacts not uploaded
if: always())Issue: "Invalid workflow file"
yamllint or GitHub's schema# Enable debug logging
- name: Debug information
run: |
echo "Event name: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "Repository: ${{ github.repository }}"
echo "Runner OS: ${{ runner.os }}"
echo "Working directory: $(pwd)"
ls -la
# Add step summaries
- name: Generate summary
run: |
echo "## Audit Results" >> $GITHUB_STEP_SUMMARY
echo "- Critical issues: 2" >> $GITHUB_STEP_SUMMARY
echo "- High issues: 5" >> $GITHUB_STEP_SUMMARY
When reviewing a workflow, provide feedback in this structure:
# Workflow Review: [workflow-name.yml]
**Type**: [Universal Reusable Workflow / Composite Action / Direct Workflow]
**Purpose**: [Brief description]
---
## ✅ Strengths
- [What the workflow does well]
- [Good patterns observed]
---
## ⚠️ Issues Found
### 🔴 Critical
**Issue**: [Description]
**Location**: `line X-Y`
**Impact**: [What could go wrong]
**Fix**:
```yaml
# Current (problematic)
[current code]
# Recommended
[fixed code]
Suggestion: [Description] Benefit: [Why this improves the workflow] Implementation:
[suggested code]
[Any context-specific guidance or references]
<!-- GITHUB-WORKFLOWS:END -->
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.