mcpField
M
make-github-actions-workflow

make-github-actions-workflow

github.com/dotnetCloud & DevOps14.8k46 installsUpdated today
Repository

About make-github-actions-workflow

A skill for creating GitHub Actions workflows that follow the repository's conventions. It guides event selection, permissions, safe scripting, guard conditions, error handling, and validation for CI and repository automation.

Key features

  1. 01Select workflow trigger events
  2. 02Declare minimal explicit permissions
  3. 03Add guard conditions and idempotency checks
  4. 04Use `actions/github-script@v8` for complex logic
  5. 05Handle API errors and pagination
  6. 06Validate workflow security and behavior

Use cases

  1. 01Creating CI validation workflows
  2. 02Automating pull request management
  3. 03Adding issue labeling or notifications
  4. 04Reviewing workflow permissions and safety

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/dotnet/efcore --skill make-github-actions-workflow

After installing, type/make-github-actions-workflowin Claude Code to confirm it loaded.

Details

From the project README

Create GitHub Actions Workflow

Create and configure GitHub Actions workflows that follow this repository's conventions and patterns. Workflows automate tasks like CI validation, PR management, issue labeling, and notifications.

Repository Conventions

This repository uses specific patterns across all workflows. Follow these conventions when creating new workflows:

File Location and Naming
  • All workflows live in .github/workflows/
  • Use kebab-case names: label-and-milestone-issues.yml, validate-pr-target-branch.yml
  • Start with a descriptive comment block explaining the workflow's purpose
Runner
  • Use ubuntu-latest for general-purpose jobs
  • Use ubuntu-24.04 when a specific OS version is needed (e.g., copilot setup steps)
Permissions
  • Always declare explicit permissions — never rely on defaults
  • Use minimal permissions: only request what the workflow needs
  • Common patterns:
    • Read-only: permissions: {}
    • PR comments: pull-requests: write
    • Issue management: issues: write
    • Code read: contents: read
  • For workflows that need write access to PRs from external contributors, use pull_request_target instead of pull_request
    • Security warning: pull_request_target workflows run in the base repository context with its permissions. Do not check out or execute code from the PR branch in these workflows. Instead, use the event payload and/or GitHub API (actions/github-script, github.rest.*, etc.) to inspect and act on the PR safely.
Scripting
  • Use actions/github-script@v8 for complex logic instead of shell scripts
  • Write JavaScript with async/await — the github object (Octokit) and context are available
  • Use github.rest.* for REST API calls and github.graphql() for GraphQL queries
  • Access PR/issue data via context.payload.pull_request, context.repo.owner, context.repo.repo
  • Log with console.log() for debugging; use core.setFailed() for errors
Error Handling
  • Wrap API calls in try/catch blocks
  • Collect errors and report at the end rather than failing on the first error
  • Log skipped operations with a reason

Workflow

Step 1: Understand the Requirements

Before writing the workflow, clarify:

  • What event(s) should trigger it? (push, pull_request, check_suite, schedule, etc.)
  • What permissions are needed?
  • What conditions should gate execution? (author, branch, labels, etc.)
  • What API calls are needed?
  • Are there rate limiting or idempotency concerns?
Step 2: Choose the Trigger Event
Use caseEventNotes
PR opened/updatedpull_requestRead-only access to PR
PR with write accesspull_request_targetCan comment, label, close
PR mergedpull_request_target: [closed]Check github.event.pull_request.merged == true
CI checks completecheck_suite: [completed]Filter app.slug != 'github-actions' to skip self
Code pushedpushFilter by branches/paths
Manual triggerworkflow_dispatchAdd inputs: for parameters
ScheduledscheduleUse cron syntax
Step 3: Write the Workflow

Create the YAML file following this template:

# Description of what this workflow does and why.

name: Descriptive Workflow Name

on:
  <trigger_event>:
    types: [<event_types>]

permissions:
  <resource>: <read|write>

jobs:
  <job-name>:
    if: <guard_condition>  # Optional: skip when not needed
    runs-on: ubuntu-latest
    steps:
      - name: Descriptive step name
        uses: actions/github-script@v8
        with:
          script: |
            // Your logic here
Step 4: Implement Guard Conditions

Add conditions to avoid unnecessary runs:

  • Job-level if: for event payload checks (e.g., github.event.check_suite.app.slug != 'github-actions')
  • Early return in scripts for PR author, draft status, etc.
  • Idempotency checks (e.g., hidden HTML comments as tags to prevent duplicate actions)

For pull_request_target policy workflows that react to opened, edited, and reopened:

  • On edited, return unless context.payload.changes?.base is present; title or body edits must not reapply a target-branch policy.
  • Check permissions for context.actor, not only the PR author. A maintainer may edit or reopen an external PR, and that action must use the maintainer's authority.
  • For opened, the actor and author are the same, so perform one permission lookup and reuse it for policy and labeling decisions.
  • Exclude automation when either the actor or author is a bot.
  • Encode the prohibited branch class directly (for example, release/*) instead of requiring a single allowed branch such as main.
  • If permission lookup fails, stop without taking a destructive action; unknown access is not evidence that the contributor is external.
Step 5: Validate

After creating the workflow:

  • Permissions are minimal and explicitly declared
  • Uses actions/github-script@v8 for complex logic
  • Guard conditions prevent unnecessary runs
  • API calls handle pagination where needed (use per_page: 100)
  • Error handling doesn't silently swallow failures
  • No secrets or tokens are hardcoded
  • Workflow doesn't create infinite loops (e.g., a workflow that triggers itself)

Existing Workflows in This Repository

Study these workflows as reference:

WorkflowTriggerPurpose
validate-pr-target-branch.ymlpull_request_target: [opened, edited, reopened]Closes external PRs targeting release branches, adds community labels
label-and-milestone-issues.ymlpull_request_target: [closed]Labels and milestones issues when their fixing PR is merged
copilot-setup-steps.ymlworkflow_dispatch, push, pull_requestSets up the development environment for Copilot
inter-branch-merge-flow.ymlpush to release/**Triggers inter-branch merge via arcade

Common Patterns

Commenting on a PR
await github.rest.issues.createComment({
  owner: context.repo.owner,
  repo: context.repo.repo,
  issue_number: prNumber,
  body: 'Comment text'
});
Checking User Permissions
const { data: permissions } = await github.rest.repos.getCollaboratorPermissionLevel({
  owner: context.repo.owner,
  repo: context.repo.repo,
  username: context.actor
});
const hasWriteAccess = ['admin', 'write'].includes(permissions.permission);
Updating Bot-Owned Comments Idempotently

A hidden marker identifies the workflow's comment but does not establish ownership: contributors and other apps can place the same marker in their comments. Match both the marker and the exact github-actions[bot] login, then update that comment or create a new one. A generic user.type === 'Bot' check does not establish workflow ownership. Preserve the workflow's pinned action SHA and never execute code from a pull-request fork in a pull_request_target job.

const marker = '<!-- validate-pr-target-branch -->';
const comments = await github.paginate(github.rest.issues.listComments, {
  owner: context.repo.owner,
  repo: context.repo.repo,
  issue_number: prNumber,
  per_page: 100
});

const body = `${marker}\nComment text`;
const existing = comments.find(comment =>
  comment.user?.login === 'github-actions[bot]' && comment.body?.includes(marker));

if (existing) {
  await github.rest.issues.updateComment({
    owner: context.repo.owner,
    repo: context.repo.repo,
    comment_id: existing.id,
    body
  });
} else {
  await github.rest.issues.createComment({
    owner: context.repo.owner,
    repo: context.repo.repo,
    issue_number: prNumber,
    body
  });
}
Reading Repository Files at a Specific Commit
const { data: fileData } = await github.rest.repos.getContent({
  owner, repo,
  path: 'eng/Versions.props',
  ref: commitSha
});
const content = Buffer.from(fileData.content, 'base64').toString('utf-8');

References

Comments

Markdown supported