actions/slash-commands/action.yml
Claude d37cc0a6dc
slash-commands: add hardened Dependabot auto-approve mode
Opt-in via new 'dependabot-auto-approve' input (mode 'dev-patch-minor').
When enabled, Dependabot PRs are evaluated on pull_request_target
opened/reopened and on check_suite completed for dependabot/ branches,
and approved + labeled + auto-merged only when ALL gates hold:

1. Author is the real dependabot[bot] app (login AND user id 49699333)
2. Same-repo head on a dependabot/ branch (never forks)
3. Every commit signature-verified and dependabot-authored
4. Changed files a subset of 'dependabot-allowed-files' (exact paths)
5. Every updated dependency direct:development with semver-patch/minor
   update per Dependabot's commit metadata (missing metadata = no approve)
6. CI green on head SHA: all check runs success/neutral/skipped with at
   least one non-skipped check, combined status success if any exist
7. PR open, non-draft, conflict-free, not on hold

Every gate fails closed, logging the reason to the action log only.
Approvals use a distinct marker (slash-commands-dependabot-auto) that
push invalidation now dismisses alongside /lgtm approvals; after a human
push, gate 3 fails permanently so only /lgtm can accept the PR.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ygNtiNcAaqTRnQjHCDkVS
2026-07-06 17:38:34 +00:00

179 lines
7.6 KiB
YAML

name: PR Slash Commands
description: >-
Handles /lgtm, /hold, and /stageblog slash commands on PRs. Core maintainers
can approve via /lgtm (adds an accepted label, submits a bot APPROVE review,
and enables auto-merge). Core maintainers or CODEOWNERS can block via /hold
or trigger a blog preview staging workflow via /stageblog. Approval is
invalidated when new commits are pushed. Optionally auto-approves
narrowly-scoped Dependabot PRs (dependabot-auto-approve).
inputs:
github-token:
description: >-
GitHub App installation token used for all API calls: team-membership
checks, labels, comments, reactions, reviews, auto-merge, CODEOWNERS
fetch, and workflow dispatch. Must have Organization Members:read plus
Repository Pull requests:write and Contents:read (and Actions:write if
/stageblog is enabled). The default GITHUB_TOKEN will NOT work — it
lacks read:org and its approvals may not count toward required reviews.
required: true
approved-label:
description: Label added by /lgtm to mark the PR as accepted
required: false
default: accepted
hold-label:
description: Label added by /hold to block merging
required: false
default: do-not-merge/hold
always-allow-teams:
description: >-
Comma-separated team slugs (within the repo's org) whose members may
/lgtm any PR. Only these teams can /lgtm — CODEOWNERS does not grant
/lgtm authorization. Members may also /hold and /stageblog any PR.
required: false
default: core-maintainers
force-allow-teams:
description: >-
Comma-separated team slugs (within the repo's org) whose members may
use /lgtm force. This bypasses the self-approval guard and CODEOWNERS —
only membership in one of these teams grants it.
required: false
default: core-maintainers,lead-maintainers
codeowners-path:
description: Path to the CODEOWNERS file (fetched from the PR base branch)
required: false
default: .github/CODEOWNERS
invalidate-on-push:
description: >-
Remove the approved label and dismiss the bot review when new commits
are pushed to the PR. Skipped if the label was most recently applied
manually by a member of always-allow-teams or force-allow-teams (not
the bot via /lgtm). Set to 'false' to keep approval across pushes
unconditionally.
required: false
default: 'true'
submit-review:
description: >-
Whether to submit a bot APPROVE review alongside the label on /lgtm.
Set to 'false' to rely on the label alone.
required: false
default: 'true'
enable-auto-merge:
description: >-
Enable auto-merge on the PR after a successful /lgtm. The PR will merge
automatically once all required checks pass. Requires the repo to have
"Allow auto-merge" enabled in Settings. Disabled on /lgtm cancel and on
push invalidation.
required: false
default: 'true'
auto-merge-method:
description: Merge method for auto-merge — one of squash, merge, rebase
required: false
default: squash
remove-labels-on-accept:
description: >-
Comma-separated label names to remove from the PR when /lgtm accepts it
(e.g. in-review,draft,proposal). Missing labels are ignored.
required: false
default: ''
project-number:
description: >-
Org-level Project (V2) number to update when /lgtm accepts the PR. Leave
empty to disable project updates. The PR is added to the project if not
already present.
required: false
default: ''
project-status-field:
description: Name of the single-select field to set on accept
required: false
default: Status
project-accepted-option:
description: Name of the option to set in the status field on accept
required: false
default: Accepted
project-gate-label:
description: >-
If set, only update the project status when the PR has this label.
Leave empty to update the project for every accepted PR.
required: false
default: ''
stageblog-workflow:
description: >-
Workflow file name (e.g. stage-blog.yml) to dispatch when /stageblog is
invoked. Leave empty to disable the command. The workflow must accept
inputs 'pr_number' and 'head_sha'.
required: false
default: ''
stageblog-paths:
description: >-
Comma-separated glob patterns (CODEOWNERS-style: **, *, ?, leading /,
trailing /) matched against the PR's changed files. /stageblog is refused
if no file matches.
required: false
default: 'blog/**'
dependabot-auto-approve:
description: >-
Opt-in Dependabot auto-approve policy. Empty (default) = disabled.
'dev-patch-minor' approves + labels + enables auto-merge on Dependabot
PRs where ALL of these hold: author is the real dependabot[bot] app
(login and user id), same-repo dependabot/ head branch, every commit is
signature-verified and dependabot-authored, changed files are a subset
of dependabot-allowed-files, every updated dependency is
direct:development with a semver-patch/minor update (per Dependabot's
commit-message metadata; missing metadata never approves), all check
runs on the head SHA are green with at least one non-skipped check, and
the PR is open, non-draft, conflict-free, and not on hold. Requires the
caller workflow to also trigger on pull_request_target
[opened, reopened] and check_suite [completed]. Any other value
disables the mode (fail closed).
required: false
default: ''
dependabot-allowed-files:
description: >-
Comma-separated exact file paths a Dependabot PR may touch to qualify
for auto-approve. A PR changing any other file is never auto-approved.
required: false
default: 'package.json,package-lock.json'
outputs:
result:
description: >-
One of: lgtm-added, lgtm-forced, lgtm-removed, hold-added, hold-removed,
invalidated, invalidate-skipped, unauthorized, force-unauthorized,
self-lgtm-blocked, stageblog-dispatched, stageblog-not-blog,
stageblog-disabled, dependabot-approved, dependabot-skipped, noop
value: ${{ steps.handle.outputs.result }}
actor:
description: Login of the user who triggered the action (commenter)
value: ${{ steps.handle.outputs.actor }}
runs:
using: composite
steps:
- name: Handle slash command
id: handle
uses: actions/github-script@v8
env:
APPROVED_LABEL: ${{ inputs.approved-label }}
HOLD_LABEL: ${{ inputs.hold-label }}
ALWAYS_ALLOW_TEAMS: ${{ inputs.always-allow-teams }}
FORCE_ALLOW_TEAMS: ${{ inputs.force-allow-teams }}
CODEOWNERS_PATH: ${{ inputs.codeowners-path }}
INVALIDATE_ON_PUSH: ${{ inputs.invalidate-on-push }}
SUBMIT_REVIEW: ${{ inputs.submit-review }}
ENABLE_AUTO_MERGE: ${{ inputs.enable-auto-merge }}
AUTO_MERGE_METHOD: ${{ inputs.auto-merge-method }}
REMOVE_LABELS_ON_ACCEPT: ${{ inputs.remove-labels-on-accept }}
PROJECT_NUMBER: ${{ inputs.project-number }}
PROJECT_STATUS_FIELD: ${{ inputs.project-status-field }}
PROJECT_ACCEPTED_OPTION: ${{ inputs.project-accepted-option }}
PROJECT_GATE_LABEL: ${{ inputs.project-gate-label }}
STAGEBLOG_WORKFLOW: ${{ inputs.stageblog-workflow }}
STAGEBLOG_PATHS: ${{ inputs.stageblog-paths }}
DEPENDABOT_AUTO_APPROVE: ${{ inputs.dependabot-auto-approve }}
DEPENDABOT_ALLOWED_FILES: ${{ inputs.dependabot-allowed-files }}
with:
github-token: ${{ inputs.github-token }}
script: |
const main = require(process.env.GITHUB_ACTION_PATH + '/main.js');
await main({ github, context, core });