## Summary Forked repositories inherit GitHub Actions workflows including scheduled ones. This causes: 1. **Wasted Actions minutes** - Scheduled workflows run on forks even though they will fail 2. **Failed runs** - Workflows requiring `CODEX_OPENAI_API_KEY` fail immediately on forks 3. **Noise** - Fork owners see failed workflow runs they didn't trigger This PR adds `if: github.repository == 'openai/codex'` guards to workflows that should only run on the upstream repository. ### Affected workflows | Workflow | Trigger | Issue | |----------|---------|-------| | `rust-release-prepare` | `schedule: */4 hours` | Runs 6x/day on every fork | | `close-stale-contributor-prs` | `schedule: daily` | Runs daily on every fork | | `issue-deduplicator` | `issues: opened` | Requires `CODEX_OPENAI_API_KEY` | | `issue-labeler` | `issues: opened` | Requires `CODEX_OPENAI_API_KEY` | ### Note `cla.yml` already has this guard (`github.repository_owner == 'openai'`), so it was not modified. ## Test plan - [ ] Verify workflows still run correctly on `openai/codex` - [ ] Verify workflows are skipped on forks (can check via Actions tab on any fork)
107 lines
3.4 KiB
YAML
107 lines
3.4 KiB
YAML
name: Close stale contributor PRs
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
schedule:
|
|
- cron: "0 6 * * *"
|
|
|
|
permissions:
|
|
contents: read
|
|
issues: write
|
|
pull-requests: write
|
|
|
|
jobs:
|
|
close-stale-contributor-prs:
|
|
# Prevent scheduled runs on forks
|
|
if: github.repository == 'openai/codex'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Close inactive PRs from contributors
|
|
uses: actions/github-script@v8
|
|
with:
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
script: |
|
|
const DAYS_INACTIVE = 14;
|
|
const cutoff = new Date(Date.now() - DAYS_INACTIVE * 24 * 60 * 60 * 1000);
|
|
const { owner, repo } = context.repo;
|
|
const dryRun = false;
|
|
const stalePrs = [];
|
|
|
|
core.info(`Dry run mode: ${dryRun}`);
|
|
|
|
const prs = await github.paginate(github.rest.pulls.list, {
|
|
owner,
|
|
repo,
|
|
state: "open",
|
|
per_page: 100,
|
|
sort: "updated",
|
|
direction: "asc",
|
|
});
|
|
|
|
for (const pr of prs) {
|
|
const lastUpdated = new Date(pr.updated_at);
|
|
if (lastUpdated > cutoff) {
|
|
core.info(`PR ${pr.number} is fresh`);
|
|
continue;
|
|
}
|
|
|
|
if (!pr.user || pr.user.type !== "User") {
|
|
core.info(`PR ${pr.number} wasn't created by a user`);
|
|
continue;
|
|
}
|
|
|
|
let permission;
|
|
try {
|
|
const permissionResponse = await github.rest.repos.getCollaboratorPermissionLevel({
|
|
owner,
|
|
repo,
|
|
username: pr.user.login,
|
|
});
|
|
permission = permissionResponse.data.permission;
|
|
} catch (error) {
|
|
if (error.status === 404) {
|
|
core.info(`Author ${pr.user.login} is not a collaborator; skipping #${pr.number}`);
|
|
continue;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
const hasContributorAccess = ["admin", "maintain", "write"].includes(permission);
|
|
if (!hasContributorAccess) {
|
|
core.info(`Author ${pr.user.login} has ${permission} access; skipping #${pr.number}`);
|
|
continue;
|
|
}
|
|
|
|
stalePrs.push(pr);
|
|
}
|
|
|
|
if (!stalePrs.length) {
|
|
core.info("No stale contributor pull requests found.");
|
|
return;
|
|
}
|
|
|
|
for (const pr of stalePrs) {
|
|
const issue_number = pr.number;
|
|
const closeComment = `Closing this pull request because it has had no updates for more than ${DAYS_INACTIVE} days. If you plan to continue working on it, feel free to reopen or open a new PR.`;
|
|
|
|
if (dryRun) {
|
|
core.info(`[dry-run] Would close contributor PR #${issue_number} from ${pr.user.login}`);
|
|
continue;
|
|
}
|
|
|
|
await github.rest.issues.createComment({
|
|
owner,
|
|
repo,
|
|
issue_number,
|
|
body: closeComment,
|
|
});
|
|
|
|
await github.rest.pulls.update({
|
|
owner,
|
|
repo,
|
|
pull_number: issue_number,
|
|
state: "closed",
|
|
});
|
|
|
|
core.info(`Closed contributor PR #${issue_number} from ${pr.user.login}`);
|
|
}
|