mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-09 10:17:00 +00:00
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).
147 lines
6.9 KiB
YAML
147 lines
6.9 KiB
YAML
# 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.`);
|