mirror of
https://github.com/hwchase17/langchain.git
synced 2026-03-18 11:07:36 +00:00
ci: bypass issue-link gate for trusted contributors (#35720)
Bypass the issue-link requirement for external contributors who have earned the `trusted-contributor` tier label (>=5 merged PRs). Previously only PRs with the `internal` label skipped the gate, meaning repeat contributors still had to link an approved issue on every PR. Also includes minor template and linting tweaks for contributor experience. ## Changes - Add `trusted-contributor` bypass to the `check-issue-link` job condition in `require_issue_link.yml`, with a secondary live-label API fetch inside the script to cover the race where the `external` labeled event payload doesn't yet include the tier label - Add a `bypass-trusted-contributor` job in `require_issue_link.yml` that removes `missing-issue-link` and reopens the PR when the `trusted-contributor` label arrives after enforcement has already closed it - Reorder steps in `tag-external-contributions.yml` so the tier label is applied *before* the `external` label — eliminates the race window entirely since `trusted-contributor` is already on the PR when the downstream `labeled` event fires - Switch the tier-label step from `GITHUB_TOKEN` to the app token so the `trusted-contributor` labeled event propagates to downstream workflows - Add `hotfix` to allowed PR title types in `pr_lint.yml` - Promote the English language policy to a blockquote callout in issue and PR templates; add a "do not begin work without assignment" note to the feature request template
This commit is contained in:
2
.github/workflows/pr_lint.yml
vendored
2
.github/workflows/pr_lint.yml
vendored
@@ -25,6 +25,7 @@
|
||||
# * chore — other changes that don't modify source or test files
|
||||
# * revert — reverts a previous commit
|
||||
# * release — prepare a new release
|
||||
# * hotfix — urgent fix
|
||||
#
|
||||
# Allowed Scope(s) (optional):
|
||||
# core, langchain, langchain-classic, model-profiles,
|
||||
@@ -83,6 +84,7 @@ jobs:
|
||||
chore
|
||||
revert
|
||||
release
|
||||
hotfix
|
||||
scopes: |
|
||||
core
|
||||
langchain
|
||||
|
||||
60
.github/workflows/require_issue_link.yml
vendored
60
.github/workflows/require_issue_link.yml
vendored
@@ -5,6 +5,8 @@
|
||||
# - Reacts to the "external" label applied by tag-external-contributions.yml,
|
||||
# avoiding a duplicate org membership check.
|
||||
# - Also re-checks on PR edits/reopens for PRs that already have the label.
|
||||
# - Bypasses the check for PRs with the "trusted-contributor" label, and
|
||||
# automatically reopens/cleans up PRs that receive it after enforcement.
|
||||
# - Validates the PR author is an assignee on at least one linked issue.
|
||||
# - Adds a "missing-issue-link" label on failure; removes it on pass.
|
||||
# - Automatically reopens PRs that were closed by this workflow once the
|
||||
@@ -28,10 +30,14 @@ permissions:
|
||||
|
||||
jobs:
|
||||
check-issue-link:
|
||||
# Run when the "external" label is added, or on edit/reopen if already labeled
|
||||
# Run when the "external" label is added, or on edit/reopen if already labeled.
|
||||
# Skip entirely when the PR already carries "trusted-contributor".
|
||||
if: >-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'external') ||
|
||||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'external'))
|
||||
!contains(github.event.pull_request.labels.*.name, 'trusted-contributor') &&
|
||||
(
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'external') ||
|
||||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'external'))
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
@@ -42,6 +48,21 @@ jobs:
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
|
||||
// Fetch live labels to handle the race where "external" fires
|
||||
// before "trusted-contributor" appears in the event payload.
|
||||
const { data: liveLabels } = await github.rest.issues.listLabelsOnIssue({
|
||||
owner, repo, issue_number: prNumber,
|
||||
});
|
||||
if (liveLabels.some(l => l.name === 'trusted-contributor')) {
|
||||
console.log('PR has trusted-contributor label — bypassing issue link check');
|
||||
core.setOutput('has-link', 'true');
|
||||
core.setOutput('is-assigned', 'true');
|
||||
return;
|
||||
}
|
||||
|
||||
const body = context.payload.pull_request.body || '';
|
||||
const pattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*#(\d+)/gi;
|
||||
const matches = [...body.matchAll(pattern)];
|
||||
@@ -58,7 +79,6 @@ jobs:
|
||||
core.setOutput('has-link', 'true');
|
||||
|
||||
// Check whether the PR author is assigned to at least one linked issue
|
||||
const { owner, repo } = context.repo;
|
||||
const prAuthor = context.payload.pull_request.user.login;
|
||||
const issueNumbers = [...new Set(matches.map(m => parseInt(m[1], 10)))];
|
||||
|
||||
@@ -198,3 +218,35 @@ jobs:
|
||||
? 'PR must reference an issue using auto-close keywords (e.g., "Fixes #123").'
|
||||
: 'PR author must be assigned to the linked issue.';
|
||||
core.setFailed(reason);
|
||||
|
||||
# When a trusted-contributor label is added to a PR that was previously
|
||||
# closed by check-issue-link, clean up and reopen it.
|
||||
bypass-trusted-contributor:
|
||||
if: github.event.action == 'labeled' && github.event.label.name == 'trusted-contributor'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Remove missing-issue-link label and reopen PR
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner, repo, issue_number: prNumber, name: 'missing-issue-link',
|
||||
});
|
||||
console.log('Removed missing-issue-link label');
|
||||
} catch (error) {
|
||||
if (error.status !== 404) throw error;
|
||||
}
|
||||
|
||||
if (context.payload.pull_request.state === 'closed') {
|
||||
await github.rest.pulls.update({
|
||||
owner, repo, pull_number: prNumber, state: 'open',
|
||||
});
|
||||
console.log(`Reopened PR #${prNumber}`);
|
||||
}
|
||||
|
||||
93
.github/workflows/tag-external-contributions.yml
vendored
93
.github/workflows/tag-external-contributions.yml
vendored
@@ -97,6 +97,55 @@ jobs:
|
||||
}
|
||||
}
|
||||
|
||||
# Apply tier label BEFORE the external/internal labels so that
|
||||
# "trusted-contributor" is already present when the "external" labeled
|
||||
# event fires and triggers require_issue_link.yml.
|
||||
- name: Apply contributor tier label
|
||||
if: steps.check-membership.outputs.is-external == 'true'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
# Use App token so the "labeled" event propagates to downstream
|
||||
# workflows (e.g. require_issue_link.yml bypass-trusted-contributor).
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const isPR = context.eventName === 'pull_request_target';
|
||||
const item = isPR
|
||||
? context.payload.pull_request
|
||||
: context.payload.issue;
|
||||
const author = item.user.login;
|
||||
const issueNumber = item.number;
|
||||
|
||||
const TRUSTED_THRESHOLD = 5;
|
||||
|
||||
const mergedQuery = `repo:${owner}/${repo} is:pr is:merged author:"${author}"`;
|
||||
let mergedCount = 0;
|
||||
try {
|
||||
const result = await github.rest.search.issuesAndPullRequests({
|
||||
q: mergedQuery,
|
||||
per_page: 1,
|
||||
});
|
||||
mergedCount = result?.data?.total_count ?? 0;
|
||||
} catch (error) {
|
||||
if (error?.status !== 422) throw error;
|
||||
core.warning(`Search failed for ${author}; skipping tier label.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const label = mergedCount >= TRUSTED_THRESHOLD ? 'trusted-contributor' : null;
|
||||
|
||||
if (label) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
labels: [label],
|
||||
});
|
||||
console.log(`Applied '${label}' to #${issueNumber} (${mergedCount} merged PRs)`);
|
||||
} else {
|
||||
console.log(`No tier label for ${author} (${mergedCount} merged PRs)`);
|
||||
}
|
||||
|
||||
- name: Add external label to issue
|
||||
if: steps.check-membership.outputs.is-external == 'true' && github.event_name == 'issues'
|
||||
uses: actions/github-script@v8
|
||||
@@ -172,50 +221,6 @@ jobs:
|
||||
|
||||
console.log(`Added 'internal' label to pull request #${pull_number}`);
|
||||
|
||||
- name: Apply contributor tier label
|
||||
if: steps.check-membership.outputs.is-external == 'true'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const isPR = context.eventName === 'pull_request_target';
|
||||
const item = isPR
|
||||
? context.payload.pull_request
|
||||
: context.payload.issue;
|
||||
const author = item.user.login;
|
||||
const issueNumber = item.number;
|
||||
|
||||
const TRUSTED_THRESHOLD = 5;
|
||||
|
||||
const mergedQuery = `repo:${owner}/${repo} is:pr is:merged author:"${author}"`;
|
||||
let mergedCount = 0;
|
||||
try {
|
||||
const result = await github.rest.search.issuesAndPullRequests({
|
||||
q: mergedQuery,
|
||||
per_page: 1,
|
||||
});
|
||||
mergedCount = result?.data?.total_count ?? 0;
|
||||
} catch (error) {
|
||||
if (error?.status !== 422) throw error;
|
||||
core.warning(`Search failed for ${author}; skipping tier label.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const label = mergedCount >= TRUSTED_THRESHOLD ? 'trusted-contributor' : null;
|
||||
|
||||
if (label) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
labels: [label],
|
||||
});
|
||||
console.log(`Applied '${label}' to #${issueNumber} (${mergedCount} merged PRs)`);
|
||||
} else {
|
||||
console.log(`No tier label for ${author} (${mergedCount} merged PRs)`);
|
||||
}
|
||||
|
||||
backfill:
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
Reference in New Issue
Block a user