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:
Mason Daugherty
2026-03-10 12:01:07 -04:00
committed by GitHub
parent 292d0bda86
commit cb50fed2bb
6 changed files with 116 additions and 51 deletions

View File

@@ -6,7 +6,9 @@ body:
- type: markdown
attributes:
value: |
Thank you for taking the time to file a bug report. All issues must be written in English. See our [language policy](https://docs.langchain.com/oss/python/contributing/overview#language-policy).
> **All contributions must be in English.** See the [language policy](https://docs.langchain.com/oss/python/contributing/overview#language-policy).
Thank you for taking the time to file a bug report.
For usage questions, feature requests and general design questions, please use the [LangChain Forum](https://forum.langchain.com/).

View File

@@ -6,7 +6,9 @@ body:
- type: markdown
attributes:
value: |
Thank you for taking the time to request a new feature. All contributions must be in English. See our [language policy](https://docs.langchain.com/oss/python/contributing/overview#language-policy).
> **All contributions must be in English.** See the [language policy](https://docs.langchain.com/oss/python/contributing/overview#language-policy).
Thank you for taking the time to request a new feature.
Use this to request NEW FEATURES or ENHANCEMENTS in LangChain. For bug reports, please use the bug report template. For usage questions and general design questions, please use the [LangChain Forum](https://forum.langchain.com/).
@@ -18,6 +20,8 @@ body:
* [LangChain ChatBot](https://chat.langchain.com/)
* [GitHub search](https://github.com/langchain-ai/langchain),
* [LangChain Forum](https://forum.langchain.com/),
**Note:** Do not begin work on a PR unless explicitly assigned to this issue by a maintainer.
- type: checkboxes
id: checks
attributes:

View File

@@ -4,7 +4,7 @@ Fixes #
Read the full contributing guidelines: https://docs.langchain.com/oss/python/contributing/overview
All contributions must be in English. See our [language policy](https://docs.langchain.com/oss/python/contributing/overview#language-policy).
> **All contributions must be in English.** See the [language policy](https://docs.langchain.com/oss/python/contributing/overview#language-policy).
If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!

View File

@@ -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

View File

@@ -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}`);
}

View File

@@ -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