From 8596967415ecc406a772aab26607592f1b3df3c7 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Fri, 22 May 2026 11:14:52 -0500 Subject: [PATCH] ci: "fix: exempt team members from compliance cleanup" (#28865) --- .github/workflows/compliance-close.yml | 14 -------------- .github/workflows/duplicate-issues.yml | 4 ++-- .github/workflows/pr-management.yml | 15 ++++++--------- .github/workflows/pr-standards.yml | 24 ++++++++++++++++++------ script/github/close-issues.ts | 7 ------- script/github/close-prs.ts | 23 +++++------------------ 6 files changed, 31 insertions(+), 56 deletions(-) diff --git a/.github/workflows/compliance-close.yml b/.github/workflows/compliance-close.yml index 297fe786af..14e68701e5 100644 --- a/.github/workflows/compliance-close.yml +++ b/.github/workflows/compliance-close.yml @@ -34,25 +34,11 @@ jobs: const now = Date.now(); const twoHours = 2 * 60 * 60 * 1000; - const teamAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR']; for (const item of items) { const isPR = !!item.pull_request; const kind = isPR ? 'PR' : 'issue'; - if (teamAssociations.includes(item.author_association)) { - core.info(`Skipping ${kind} #${item.number}: author association is ${item.author_association}`); - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: item.number, - name: 'needs:compliance', - }); - } catch (e) {} - continue; - } - const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, diff --git a/.github/workflows/duplicate-issues.yml b/.github/workflows/duplicate-issues.yml index 3f0ce976e1..4648a2d0c3 100644 --- a/.github/workflows/duplicate-issues.yml +++ b/.github/workflows/duplicate-issues.yml @@ -6,7 +6,7 @@ on: jobs: check-duplicates: - if: github.event.action == 'opened' && !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association) + if: github.event.action == 'opened' runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: read @@ -118,7 +118,7 @@ jobs: Remember: post at most ONE comment combining all findings. If everything is fine, post nothing." recheck-compliance: - if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance') && !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association) + if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance') runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: read diff --git a/.github/workflows/pr-management.yml b/.github/workflows/pr-management.yml index 5d526ceaf2..b6aa4e589d 100644 --- a/.github/workflows/pr-management.yml +++ b/.github/workflows/pr-management.yml @@ -11,25 +11,22 @@ jobs: contents: read pull-requests: write steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + fetch-depth: 1 + - name: Check team membership id: team-check run: | LOGIN="${{ github.event.pull_request.user.login }}" - ASSOCIATION="${{ github.event.pull_request.author_association }}" - if [ "$LOGIN" = "opencode-agent[bot]" ] || [ "$ASSOCIATION" = "OWNER" ] || [ "$ASSOCIATION" = "MEMBER" ] || [ "$ASSOCIATION" = "COLLABORATOR" ]; then + if [ "$LOGIN" = "opencode-agent[bot]" ] || grep -qxF "$LOGIN" .github/TEAM_MEMBERS; then echo "is_team=true" >> "$GITHUB_OUTPUT" echo "Skipping: $LOGIN is a team member or bot" else echo "is_team=false" >> "$GITHUB_OUTPUT" fi - - name: Checkout repository - if: steps.team-check.outputs.is_team != 'true' - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - fetch-depth: 1 - ref: ${{ github.event.pull_request.base.sha }} - - name: Setup Bun if: steps.team-check.outputs.is_team != 'true' uses: ./.github/actions/setup-bun diff --git a/.github/workflows/pr-standards.yml b/.github/workflows/pr-standards.yml index 6e8bfe25c6..06838089d3 100644 --- a/.github/workflows/pr-standards.yml +++ b/.github/workflows/pr-standards.yml @@ -28,9 +28,15 @@ jobs: // Check if author is a team member or bot if (login === 'opencode-agent[bot]') return; - const teamAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR']; - if (teamAssociations.includes(pr.author_association)) { - console.log(`Skipping: ${login} has author association ${pr.author_association}`); + const { data: file } = await github.rest.repos.getContent({ + owner: context.repo.owner, + repo: context.repo.repo, + path: '.github/TEAM_MEMBERS', + ref: 'dev' + }); + const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean); + if (members.includes(login)) { + console.log(`Skipping: ${login} is a team member`); return; } @@ -169,9 +175,15 @@ jobs: // Check if author is a team member or bot if (login === 'opencode-agent[bot]') return; - const teamAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR']; - if (teamAssociations.includes(pr.author_association)) { - console.log(`Skipping: ${login} has author association ${pr.author_association}`); + const { data: file } = await github.rest.repos.getContent({ + owner: context.repo.owner, + repo: context.repo.repo, + path: '.github/TEAM_MEMBERS', + ref: 'dev' + }); + const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean); + if (members.includes(login)) { + console.log(`Skipping: ${login} is a team member`); return; } diff --git a/script/github/close-issues.ts b/script/github/close-issues.ts index 2dbf349eb9..9e1f597951 100755 --- a/script/github/close-issues.ts +++ b/script/github/close-issues.ts @@ -15,11 +15,8 @@ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000) type Issue = { number: number updated_at: string - author_association: string } -const teamAssociations = new Set(["OWNER", "MEMBER", "COLLABORATOR"]) - const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json", @@ -66,10 +63,6 @@ async function main() { for (const i of all) { const updated = new Date(i.updated_at) if (updated < cutoff) { - if (teamAssociations.has(i.author_association)) { - console.log(`Skipping #${i.number}: author association is ${i.author_association}`) - continue - } stale.push(i.number) } else { console.log(`\nFound fresh issue #${i.number}, stopping`) diff --git a/script/github/close-prs.ts b/script/github/close-prs.ts index d74cfa0cc1..0dd8953d90 100755 --- a/script/github/close-prs.ts +++ b/script/github/close-prs.ts @@ -69,7 +69,6 @@ const maxClose = const sleepMs = requireNonNegativeInteger("sleep-ms", values["sleep-ms"]) const printLimit = requireNonNegativeInteger("print-limit", values["print-limit"]) const cutoff = subtractMonths(new Date(), ageMonths) -const teamAssociations = new Set(["OWNER", "MEMBER", "COLLABORATOR"]) const headers = { Authorization: `Bearer ${token}`, @@ -83,7 +82,6 @@ type PullRequest = { title: string url: string createdAt: string - authorAssociation: string reactionGroups: Array<{ content: string users: { @@ -147,25 +145,19 @@ async function main() { console.log(`Threshold: fewer than ${threshold} positive reactions`) const prs = await fetchOpenPullRequests() - const scored = prs.map((pr) => ({ ...pr, positiveReactions: positiveReactionCount(pr) })) - const recentCount = scored.filter((pr) => new Date(pr.createdAt) >= cutoff).length - const teamCount = scored.filter((pr) => isTeamMember(pr)).length - const matching = scored.filter( - (pr) => !isTeamMember(pr) && new Date(pr.createdAt) < cutoff && pr.positiveReactions < threshold, - ) + const recentCount = prs.filter((pr) => new Date(pr.createdAt) >= cutoff).length + const matching = prs + .map((pr) => ({ ...pr, positiveReactions: positiveReactionCount(pr) })) + .filter((pr) => new Date(pr.createdAt) < cutoff && pr.positiveReactions < threshold) const candidates = matching.filter((pr) => !hasPriorCleanup(pr)) const selected = maxClose === undefined ? candidates : candidates.slice(0, maxClose) console.log(`Fetched ${prs.length} open PRs`) console.log(`Matching cleanup criteria: ${matching.length}`) console.log(`Skipped previously cleaned PRs: ${matching.length - candidates.length}`) - console.log(`Team member PRs untouched: ${teamCount}`) console.log(`Recent PRs untouched: ${recentCount}`) console.log( - `Older PRs with at least ${threshold} positive reactions untouched: ${ - scored.filter((pr) => !isTeamMember(pr) && new Date(pr.createdAt) < cutoff && pr.positiveReactions >= threshold) - .length - }`, + `Older PRs with at least ${threshold} positive reactions untouched: ${prs.length - matching.length - recentCount}`, ) if (selected.length === 0) return @@ -213,7 +205,6 @@ async function fetchOpenPullRequests() { title url createdAt - authorAssociation reactionGroups { content users { @@ -344,10 +335,6 @@ function hasPriorCleanup(pr: PullRequest) { return pr.labels.nodes.some((label) => label.name === cleanupLabel) } -function isTeamMember(pr: PullRequest) { - return teamAssociations.has(pr.authorAssociation) -} - function requireRepo(value: string | undefined) { if (!value) throw new Error("repo is required") const [owner, name] = value.split("/")