mirror of
https://github.com/warpdotdev/warp.git
synced 2026-05-06 23:32:51 +08:00
ci: label external-contributor PRs (#9641)
## Description Adds a new GitHub Actions workflow, `Label External Contributors`, that runs whenever a pull request is opened and applies the existing `external-contributor` label when: * the PR is **not** authored by a bot (`user.type == 'Bot'` or login ending in `[bot]`), **and** * either of the following is true (logical OR): * the author is **not** a member of the `warpdotdev` GitHub organization, **or** * the PR head repository is a fork (`pr.head.repo.full_name != pr.base.repo.full_name`). The workflow triggers on `pull_request_target` so the `GITHUB_TOKEN` has the `pull-requests: write` permission needed to label PRs that come from forks. The top-level workflow token stays read-only and only the labeling job widens permissions. The script never checks out the PR's code — it only reads the event payload and calls a couple of REST endpoints — so the `pull_request_target` trigger is safe. Org membership is determined via the GitHub REST membership API. If that call cannot be performed (for example, when `GITHUB_TOKEN` lacks org-membership context for a private member), we fall back to the PR's `author_association` field (`MEMBER` or `OWNER` is treated as internal). The same heuristic was applied retroactively to all currently-open PRs in the repo. The retrospective pass evaluated 148 open PRs, skipped 20 bot-authored PRs, labeled 89 external-contributor PRs (all of which were forks from non-org members), and left 39 internal-author PRs untouched. ## Linked Issue N/A — direct request from the team Slack channel. - [x] Where appropriate, screenshots or a short video of the implementation are included below (especially for user-visible or UI changes). ## Screenshots / Videos Not applicable: this PR only adds a CI workflow. ## Testing * Validated the workflow YAML parses cleanly. * Exercised the heuristic locally across all 148 currently-open PRs (89 expected to be labeled external, 20 bots skipped, 39 internal — all matched expectations). * Verified after retrospectively applying labels that `gh pr list --state open --label external-contributor` now returns 89 PRs, matching the dry-run prediction. ## Agent Mode - [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode _Conversation: https://staging.warp.dev/conversation/cf25ce7e-4d2d-4880-a3a3-8c4242f7c0d5_ _Run: https://oz.staging.warp.dev/runs/019ddf8f-1365-7da4-9c47-75aee57c151c_ _This PR was generated with [Oz](https://warp.dev/oz)._ --------- Co-authored-by: Oz <oz-agent@warp.dev>
This commit is contained in:
96
.github/workflows/label_external_contributors.yml
vendored
Normal file
96
.github/workflows/label_external_contributors.yml
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
# ======================================================================================
|
||||
# Workflow: Label External Contributors
|
||||
# ======================================================================================
|
||||
# Usage:
|
||||
# - Runs whenever a pull request is opened.
|
||||
# - Adds the `external-contributor` label to the PR if either of the following is
|
||||
# true (logical OR), and the PR is not authored by a bot:
|
||||
# 1. The PR author is not a member of the `warpdotdev` GitHub organization.
|
||||
# 2. The PR head repository is a fork (i.e. it does not belong to the same
|
||||
# repository as the base).
|
||||
#
|
||||
# Notes:
|
||||
# - The workflow triggers on `pull_request_target` rather than `pull_request` so
|
||||
# that it has the `pull-requests: write` permission needed to apply labels even
|
||||
# when the PR is opened from a fork. Because we never check out the PR's code
|
||||
# and only read the event payload, this trigger is safe.
|
||||
# - Org membership is determined via the GitHub REST API. We fall back to the
|
||||
# PR's `author_association` field when the API call cannot be performed (for
|
||||
# example, when the GITHUB_TOKEN lacks org-membership context for private
|
||||
# members).
|
||||
# ======================================================================================
|
||||
|
||||
name: Label External Contributors
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
# Default to a read-only token. The job below widens permissions explicitly.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
label-external-contributor:
|
||||
name: Label external-contributor PRs
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Determine and apply external-contributor label
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const author = pr.user.login;
|
||||
|
||||
// Ignore PRs authored by bots.
|
||||
if (pr.user.type === 'Bot' || author.endsWith('[bot]')) {
|
||||
console.log(`Skipping bot user: ${author}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// The PR comes from a fork if its head repo differs from its base repo.
|
||||
const isFork =
|
||||
!pr.head.repo ||
|
||||
pr.head.repo.full_name !== pr.base.repo.full_name;
|
||||
|
||||
// Check whether the author is a member of the warpdotdev org. The
|
||||
// membership API requires the requester to be a member of the org.
|
||||
// When it cannot return a definitive result, fall back to the PR's
|
||||
// author_association field, which GitHub computes based on the
|
||||
// author's relationship to the repository.
|
||||
let isOrgMember =
|
||||
pr.author_association === 'MEMBER' ||
|
||||
pr.author_association === 'OWNER';
|
||||
try {
|
||||
await github.rest.orgs.checkMembershipForUser({
|
||||
org: 'warpdotdev',
|
||||
username: author,
|
||||
});
|
||||
isOrgMember = true;
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`checkMembershipForUser failed (${error.status}); ` +
|
||||
`falling back to author_association="${pr.author_association}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const isExternal = !isOrgMember || isFork;
|
||||
console.log(
|
||||
`PR #${pr.number} by ${author}: ` +
|
||||
`isFork=${isFork}, isOrgMember=${isOrgMember}, ` +
|
||||
`isExternal=${isExternal}`,
|
||||
);
|
||||
|
||||
if (!isExternal) {
|
||||
return;
|
||||
}
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels: ['external-contributor'],
|
||||
});
|
||||
console.log(`Labeled PR #${pr.number} as external-contributor`);
|
||||
Reference in New Issue
Block a user