From cb50fed2bb023090a7d8b74c11738496729daafd Mon Sep 17 00:00:00 2001 From: Mason Daugherty Date: Tue, 10 Mar 2026 12:01:07 -0400 Subject: [PATCH] ci: bypass issue-link gate for trusted contributors (#35720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/ISSUE_TEMPLATE/bug-report.yml | 4 +- .github/ISSUE_TEMPLATE/feature-request.yml | 6 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/pr_lint.yml | 2 + .github/workflows/require_issue_link.yml | 60 +++++++++++- .../workflows/tag-external-contributions.yml | 93 ++++++++++--------- 6 files changed, 116 insertions(+), 51 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 9b38370fa9f..b7df45af91c 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -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/). diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index a6373531707..9fedac888ae 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -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: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e53570b7b74..c64f571ecb6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -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! diff --git a/.github/workflows/pr_lint.yml b/.github/workflows/pr_lint.yml index 1cd47cbd59c..107a3ec811f 100644 --- a/.github/workflows/pr_lint.yml +++ b/.github/workflows/pr_lint.yml @@ -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 diff --git a/.github/workflows/require_issue_link.yml b/.github/workflows/require_issue_link.yml index c878d74faa4..50a6f02d7c7 100644 --- a/.github/workflows/require_issue_link.yml +++ b/.github/workflows/require_issue_link.yml @@ -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}`); + } diff --git a/.github/workflows/tag-external-contributions.yml b/.github/workflows/tag-external-contributions.yml index 5dfbcdb3fe1..362d2cfc8ca 100644 --- a/.github/workflows/tag-external-contributions.yml +++ b/.github/workflows/tag-external-contributions.yml @@ -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