mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-09 18:50:33 +00:00
ci(infra): port four CI governance workflows (#37511)
Four GitHub Actions workflows ported from the Deep Agents monorepo to enforce repository hygiene rules that were not previously applied here. ## Changes - **Fork-main PR guard**: closes PRs from forks whose head is `main` or `master`, with a sticky comment explaining how to reopen from a feature branch. Prevents the "Update branch" → admin-override path that lets a `Merge branch 'master' into master` commit land on the default branch and bypass squash-only policy. Maintainers can override with a `bypass-fork-main-check` label. - **Monthly uv pin bump**: opens a PR on the first of each month to advance `UV_VERSION` in the composite setup action. Probes `releases.astral.sh` across four architectures before committing so CI doesn't race a lagging mirror on fresh-release days — the gap Dependabot's `github-actions` ecosystem can't cover because it tracks `uses:` SHA pins, not the inline `UV_VERSION` value. - **Extras-sync validation**: a Python script (`check_extras_sync.py`) and companion workflow that detect version-constraint drift between `[project.dependencies]` and `[project.optional-dependencies]` across every `libs/**/pyproject.toml`. Runs on PRs touching any `pyproject.toml` and on pushes to `master`; is a no-op on packages that declare no extras. - **Banned-trailer pre-merge lint**: rejects PR descriptions containing a `Co-authored-by: ... <noreply@anthropic.com>` trailer before the PR reaches merge, where the org ruleset would reject the squash-push anyway. Posts a sticky comment with remediation steps; updates it to a "resolved" state when the trailer is removed, rather than deleting (which requires elevated token scope on fork PRs).
This commit is contained in:
146
.github/workflows/block_fork_main_prs.yml
vendored
Normal file
146
.github/workflows/block_fork_main_prs.yml
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
# Block PRs whose head ref is `main` (or `master`) from a fork. This topology
|
||||
# (`<fork>:master -> langchain-ai/langchain:master`) lets contributors click
|
||||
# "Update branch" on the PR, producing a `Merge branch 'master' into master`
|
||||
# commit on the source side that — under admin merge override — can land
|
||||
# directly on `master` as a 2-parent merge commit, bypassing the repo's
|
||||
# squash-only policy and polluting the changelog.
|
||||
#
|
||||
# `pull_request_target` is required so the job receives a token scoped to
|
||||
# write PR labels/comments on fork PRs (the standard `pull_request` token is
|
||||
# read-only for forks). This also means the job MUST NOT check out PR code —
|
||||
# see the inline warning in the trigger block below.
|
||||
#
|
||||
# Maintainer bypass: add the `bypass-fork-main-check` label to the PR.
|
||||
|
||||
name: Block fork main PRs
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
# NEVER CHECK OUT UNTRUSTED CODE FROM A PR's HEAD IN A pull_request_target JOB.
|
||||
# Doing so would allow attackers to execute arbitrary code in the context of your repository.
|
||||
types: [opened, reopened, synchronize, labeled, unlabeled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
guard:
|
||||
if: >-
|
||||
github.repository_owner == 'langchain-ai' &&
|
||||
github.event.pull_request.head.repo.fork == true &&
|
||||
(github.event.pull_request.head.ref == 'main' || github.event.pull_request.head.ref == 'master') &&
|
||||
!contains(github.event.pull_request.labels.*.name, 'bypass-fork-main-check')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Close PR and post guidance
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const headRef = context.payload.pull_request.head.ref;
|
||||
const marker = '<!-- block-fork-main -->';
|
||||
|
||||
// Ensure the warning label exists and apply it
|
||||
const labelName = 'fork-main-head';
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner, repo, name: labelName });
|
||||
} catch (e) {
|
||||
if (e.status !== 404) {
|
||||
throw new Error(`getLabel(${labelName}) failed: ${e.message}`);
|
||||
}
|
||||
try {
|
||||
await github.rest.issues.createLabel({
|
||||
owner, repo, name: labelName, color: 'b76e79',
|
||||
});
|
||||
} catch (createErr) {
|
||||
// A 422 with code `already_exists` means a race created the
|
||||
// label between getLabel and createLabel — safe to ignore.
|
||||
// Any other 422 (bad color, name too long) indicates a real
|
||||
// bug introduced by editing this step, so rethrow.
|
||||
const alreadyExists =
|
||||
createErr.status === 422 &&
|
||||
Array.isArray(createErr.errors) &&
|
||||
createErr.errors.some(e => e.code === 'already_exists');
|
||||
if (!alreadyExists) throw createErr;
|
||||
}
|
||||
}
|
||||
await github.rest.issues.addLabels({
|
||||
owner, repo, issue_number: prNumber, labels: [labelName],
|
||||
});
|
||||
|
||||
const defaultBranch = context.payload.repository.default_branch;
|
||||
const lines = [
|
||||
marker,
|
||||
`**This PR has been automatically closed** because its head branch is \`${headRef}\` on a fork.`,
|
||||
'',
|
||||
'PRs opened from a fork\'s `main` (or `master`) branch can produce a `Merge branch \'main\' into main` commit on the source side. Under an admin merge override that commit can land directly on this repo\'s default branch, bypassing the squash-only policy and polluting the changelog.',
|
||||
'',
|
||||
'To fix:',
|
||||
`1. Sync your fork's \`${defaultBranch}\` first (\`git fetch upstream && git switch ${defaultBranch} && git merge --ff-only upstream/${defaultBranch}\`)`,
|
||||
'2. Create a feature branch: `git switch -c feat/my-change`',
|
||||
'3. Push it: `git push -u origin feat/my-change`',
|
||||
`4. Open a new PR from \`feat/my-change\` → \`langchain-ai/langchain:${defaultBranch}\``,
|
||||
'',
|
||||
'*Maintainers: add the `bypass-fork-main-check` label to override.*',
|
||||
];
|
||||
const body = lines.join('\n');
|
||||
|
||||
// Dedup: update existing marker comment instead of stacking.
|
||||
const comments = await github.paginate(
|
||||
github.rest.issues.listComments,
|
||||
{ owner, repo, issue_number: prNumber, per_page: 100 },
|
||||
);
|
||||
const existing = comments.find(c => c.body && c.body.includes(marker));
|
||||
|
||||
if (!existing) {
|
||||
await github.rest.issues.createComment({
|
||||
owner, repo, issue_number: prNumber, body,
|
||||
});
|
||||
} else if (existing.body !== body) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner, repo, comment_id: existing.id, body,
|
||||
});
|
||||
}
|
||||
|
||||
if (context.payload.pull_request.state === 'open') {
|
||||
await github.rest.pulls.update({
|
||||
owner, repo, pull_number: prNumber, state: 'closed',
|
||||
});
|
||||
}
|
||||
|
||||
// Cancel still-queued/in-progress checks on this PR head.
|
||||
// Best-effort: new runs may still queue after this loop (e.g., other
|
||||
// pull_request triggers fanning out). The PR is already closed above,
|
||||
// so leftover runs are wasted compute, not a correctness issue.
|
||||
// We track the cancel ratio so a wholesale failure (token-scope
|
||||
// regression making EVERY cancel return 403) is surfaced rather
|
||||
// than silently producing N warnings + green job.
|
||||
const headSha = context.payload.pull_request.head.sha;
|
||||
let attempted = 0;
|
||||
let cancelled = 0;
|
||||
for (const status of ['in_progress', 'queued']) {
|
||||
const runs = await github.paginate(
|
||||
github.rest.actions.listWorkflowRunsForRepo,
|
||||
{ owner, repo, head_sha: headSha, status, per_page: 100 },
|
||||
);
|
||||
for (const run of runs) {
|
||||
if (run.id === context.runId) continue;
|
||||
attempted++;
|
||||
try {
|
||||
await github.rest.actions.cancelWorkflowRun({
|
||||
owner, repo, run_id: run.id,
|
||||
});
|
||||
cancelled++;
|
||||
} catch (err) {
|
||||
core.warning(`Could not cancel run ${run.id}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (attempted > 0 && cancelled === 0) {
|
||||
core.warning(`Attempted to cancel ${attempted} run(s) on head ${headSha} but none succeeded — check token scope.`);
|
||||
}
|
||||
|
||||
core.setFailed(`PR head ref is \`${headRef}\` on a fork — open from a feature branch instead.`);
|
||||
Reference in New Issue
Block a user