Files
knowledge-work-plugins/.github/workflows/close-external-prs.yml
T
Bryan ThompsonandClaude Opus 4.8 f540d5605f chore(ci): add external-PR curation workflows (port from claude-plugins-official) (#428)
knowledge-work-plugins is a curated surface, but unlike claude-plugins-official
it had no gate on external pull requests — so the open-PR queue had accumulated
~87 external contributions (README edits, "fixes" to first-party plugins,
unsolicited new community plugins) that we do not accept here.

Ports the established -official mechanism verbatim:

- close-external-prs.yml — on PR opened, if the author is not an org member
  (write/admin) and the PR is not an in-scope contribution, posts the standard
  redirect comment (submit via clau.de/plugin-directory-submission) and closes it.
- external-pr-scope-guard.yml — advisory (NOT required) check surfacing the
  in-scope carve-out for reviewers.
- .github/scripts/external-pr-scope.js — shared trust-the-source-repo logic
  (MARKETPLACE = .claude-plugin/marketplace.json, matches this repo).

Carve-out (verbatim from -official): a non-member PR may stay open only if it
ADDS marketplace.json entries whose source repo already backs a live plugin
here, and changes nothing else. Members + the bump bot are exempt. Kill switch:
repo var DISABLE_EXTERNAL_PR_CHECK=true.

Prospective only (fires on `opened`) — does not touch the existing open queue.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 19:58:31 -05:00

64 lines
2.7 KiB
YAML

name: Close External PRs
on:
pull_request_target:
types: [opened]
permissions:
pull-requests: write
issues: write
contents: read
jobs:
check-membership:
if: vars.DISABLE_EXTERNAL_PR_CHECK != 'true'
runs-on: ubuntu-latest
steps:
# pull_request_target: checks out the BASE repo (trusted), so the allowlist + shared
# script below are this repo's versions, never the fork's.
- uses: actions/checkout@v4
- name: Close PR unless author is a member or the PR is an in-scope external contribution
uses: actions/github-script@v7
with:
script: |
const author = context.payload.pull_request.user.login;
const { evaluate, isExemptAuthor } = require(`${process.env.GITHUB_WORKSPACE}/.github/scripts/external-pr-scope.js`);
// Members (write/admin) and the repo's own automation bot (bump SHA PRs) are never
// auto-closed.
const ex = await isExemptAuthor({ github, context });
if (ex.exempt) {
console.log(`${ex.reason} — allowing PR`);
return;
}
// Non-member: allow the PR to stay open ONLY if it is an in-scope external
// contribution — it adds marketplace.json entries whose source repo ALREADY backs
// a live plugin here, and changes nothing else. (No maintained allowlist: the set
// of allowed repos is derived from the live marketplace.) This grants only the
// right to open a reviewable PR; the validate + scan checks and a maintainer
// approval still gate the merge (the External PR Scope Guard is advisory signal,
// not a required check).
const result = await evaluate({ github, context });
if (result.ok && result.added.length > 0) {
console.log(`In-scope external contribution (adds: ${result.added.join(', ')}) — allowing PR.`);
return;
}
console.log(`Closing PR from ${author}: ${result.problems.join('; ') || 'out of scope'}`);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: `Thanks for your interest! This repo only accepts contributions from Anthropic team members. If you'd like to submit a plugin to the marketplace, please submit your plugin [here](https://clau.de/plugin-directory-submission).`
});
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
state: 'closed'
});