Post a comment on first-timers' pull requests (#65390)

A short summary of the most relevant parts of CONTRIBUTING.md and the
link to the full guidelines.

Release Notes:

- N/A
This commit is contained in:
Lena 2026-10-12 10:58:42 +00:00 • committed by GitHub
parent 93d7456688
commit fa1b94c6b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 192 additions and 0 deletions

View file

@ -17,6 +17,7 @@ on:
- "Maintainer Edits Nudge"
- "PR Board Meta Fields Refresh"
- "PR Issue Labeler"
- "Welcome First Contribution"
- "Guild Assignment Status"
- "Guild Stale Assignments"
- "Guild Weekly Shipped"

View file

@ -0,0 +1,72 @@
# Welcome first-time contributors with a short summary of CONTRIBUTING.md, once
# per author, when the 'first contribution' label is added to their PR.
name: Welcome First Contribution
on:
# zizmor: ignore[dangerous-triggers]
# Fork PRs must be supported, but this workflow only reads event metadata and
# checks out scripts from the trusted default branch; it never executes PR code.
pull_request_target:
types: [labeled]
workflow_dispatch:
inputs:
pr_number:
description: "PR number to welcome (must have the 'first contribution' label)"
required: true
type: number
dry-run:
description: "Print the comment instead of posting it"
type: boolean
default: true
permissions:
contents: read
jobs:
welcome:
if: >-
github.repository == 'zed-industries/zed' &&
(github.event_name == 'workflow_dispatch' ||
github.event.label.name == 'first contribution')
# Per author, so two PRs labeled at once can't both be welcomed. Job-level,
# so runs for other labels don't take the pending slot.
concurrency:
group: welcome-first-contribution-${{ github.event.pull_request.user.login || inputs.pr_number }}
cancel-in-progress: false
runs-on: namespace-profile-2x4-ubuntu-2404
timeout-minutes: 5
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
with:
app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
owner: zed-industries
repositories: zed
permission-issues: write
permission-pull-requests: read
- name: Checkout repository
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
sparse-checkout: |
script/github-welcome-first-contribution.py
script/github_helpers.py
sparse-checkout-cone-mode: false
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
- name: Install dependencies
run: pip install requests
- name: Post welcome comment
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
run: python script/github-welcome-first-contribution.py "$PR_NUMBER" ${{ inputs.dry-run && '--dry-run' || '' }}

View file

@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""
Post a welcome comment on a pull request labeled `first contribution`, unless
the community bot has already welcomed its author on any of their pull requests.
Requires:
requests (pip install requests)
Usage:
python github-welcome-first-contribution.py <pr_number> [--dry-run]
Environment variables:
GITHUB_TOKEN - GitHub token (issues: write, pull requests: read)
"""
import argparse
from github_helpers import (
github_rest_api,
github_rest_get_paginated,
post_github_comment,
)
REPO_OWNER = "zed-industries"
REPO_NAME = "zed"
FIRST_CONTRIBUTION_LABEL = "first contribution"
COMMUNITY_BOT_LOGIN = "zed-community-bot[bot]"
WELCOME_MARKER = "<!-- zed-community-automation:first-contribution-welcome -->"
SEARCH_PAGE_SIZE = 100
# The search API returns at most 1000 results which should never be relevant here but hey.
MAX_SEARCH_PAGES = 10
WELCOME_COMMENT = f"""{WELCOME_MARKER}
Thanks for your first contribution to Zed! For the best chances of merge, please read our [contributing guide](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md) if you haven't already. The key points for a first PR:
- **Show how you tested it.** Add evidence that fits the change: screenshots/video for UI changes, benchmarks for performance work, etc. Using LLMs or other help is fine, but you must understand and test the changes yourself. **Write review replies in your own words;** a human is reading them.
- **One PR at a time.** Things go smoother once your first PR lands; several open at once tend to go stale and collect the same feedback.
- **Discuss features first.** If this adds a feature that staff haven't agreed to in an issue, please start a [GitHub discussion](https://github.com/zed-industries/zed/discussions) instead of a PR.
- **Don't ping staff by username** unless they've told you that's okay."""
def search_pull_requests_by_author(author):
pull_requests = []
for page in range(1, MAX_SEARCH_PAGES + 1):
result = github_rest_api(
"GET",
"search/issues",
params={
"q": f"repo:{REPO_OWNER}/{REPO_NAME} is:pr author:{author}",
"sort": "created",
"order": "asc",
"per_page": SEARCH_PAGE_SIZE,
"page": page,
},
)
pull_requests.extend(result["items"])
if len(result["items"]) < SEARCH_PAGE_SIZE:
break
return pull_requests
def find_welcomed_pull_request(pull_requests):
for pull_request in pull_requests:
comments = github_rest_get_paginated(
f"repos/{REPO_OWNER}/{REPO_NAME}/issues/{pull_request['number']}/comments"
)
if any(
(comment["user"] or {}).get("login") == COMMUNITY_BOT_LOGIN
and WELCOME_MARKER in (comment["body"] or "")
for comment in comments
):
return pull_request
return None
def welcome_first_time_contributor(pr_number, dry_run):
pull_request = github_rest_api(
"GET", f"repos/{REPO_OWNER}/{REPO_NAME}/pulls/{pr_number}"
)
author = pull_request["user"]["login"]
if not any(
label["name"] == FIRST_CONTRIBUTION_LABEL for label in pull_request["labels"]
):
print(f"PR #{pr_number} has no '{FIRST_CONTRIBUTION_LABEL}' label")
return
# Search results can lag behind a just-opened PR, so check it explicitly.
welcomed_pull_request = find_welcomed_pull_request([pull_request])
if welcomed_pull_request is None:
other_pull_requests = [
other_pull_request
for other_pull_request in search_pull_requests_by_author(author)
if other_pull_request["number"] != pr_number
]
welcomed_pull_request = find_welcomed_pull_request(other_pull_requests)
if welcomed_pull_request is not None:
print(
f"{author} was already welcomed on PR #{welcomed_pull_request['number']}"
)
return
if dry_run:
print(f"Would post on PR #{pr_number}:\n\n{WELCOME_COMMENT}")
else:
post_github_comment(REPO_OWNER, REPO_NAME, pr_number, WELCOME_COMMENT)
if __name__ == "__main__":
argument_parser = argparse.ArgumentParser()
argument_parser.add_argument("pr_number", type=int)
argument_parser.add_argument(
"--dry-run",
action="store_true",
help="Print the comment instead of posting it",
)
arguments = argument_parser.parse_args()
welcome_first_time_contributor(arguments.pr_number, arguments.dry_run)