Merge 5299b6bc7cbcab8f117250fe014f500976ddda6f into 4290ea753912378913b1f3f54e0fc5720afeda5d

This commit is contained in:
alleria173 2026-08-10 00:00:49 -07:00 committed by GitHub
commit d185d608df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 160 additions and 0 deletions

View File

@ -0,0 +1,64 @@
name: Awaiting Response Label Sync
on:
# Fires when the PR author pushes new commits
pull_request_target:
types: [synchronize]
# Fires when someone comments on a PR (also fires for plain issues, filtered out below)
issue_comment:
types: [created]
permissions:
pull-requests: write
issues: write
contents: read
jobs:
sync-label:
# issue_comment fires for issues too, so only run it for PR comments
if: >-
github.event_name != 'issue_comment' ||
github.event.issue.pull_request != null
runs-on: ubuntu-latest
steps:
- name: Clear "awaiting response" label on author response
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #v9.0.0
env:
AWAITING_LABEL: 'awaiting response'
with:
script: |
const awaitingLabel = process.env.AWAITING_LABEL;
// Resolve the PR number for whichever event triggered this run
const prNumber = context.eventName === 'issue_comment'
? context.payload.issue.number
: context.payload.pull_request.number;
const { owner, repo } = context.repo;
// Check whether the label is already on the PR, so we don't try to
// remove something that isn't there
const { data: issue } = await github.rest.issues.get({
owner, repo, issue_number: prNumber,
});
const hasLabel = issue.labels.some(l =>
(typeof l === 'string' ? l : l.name) === awaitingLabel
);
if (!hasLabel) {
core.info('Label not present; nothing to do.');
return;
}
// The author pushed new commits -> treat that as their response and clear the label
const authorPushed = context.eventName === 'pull_request_target' && context.payload.action === 'synchronize';
// The PR author left a comment -> treat any reply from them as a response too
const authorCommented = context.eventName === 'issue_comment' && context.payload.comment.user.login === context.payload.issue.user.login;
if (authorPushed || authorCommented) {
// If the label was already gone for some reason, that's fine, not an error
await github.rest.issues.removeLabel({
owner, repo, issue_number: prNumber, name: awaitingLabel,
}).catch(e => core.warning(`removeLabel failed: ${e.message}`));
core.info(`Removed "${awaitingLabel}".`);
} else {
core.info('Event does not require a label change.');
}

View File

@ -0,0 +1,96 @@
name: Awaiting Response Scheduled Scan
on:
schedule:
# Runs daily at 02:45 UTC (offset from the 01:30 CLA stale scan).
- cron: '45 2 * * *'
workflow_dispatch: {}
permissions:
pull-requests: write
issues: write
contents: read
env:
AWAITING_LABEL: 'awaiting response'
# Cap on PRs examined per run, most recently updated first.
MAX_PRS: 200
jobs:
scan:
runs-on: ubuntu-latest
steps:
- name: Label open PRs that are awaiting an author response
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #v9.0.0
with:
script: |
const awaitingLabel = process.env.AWAITING_LABEL;
const maxPrs = parseInt(process.env.MAX_PRS, 10);
const { owner, repo } = context.repo;
// This scan only ADDS the label. Removal is handled by the
// event-driven workflow (awaiting-response-label.yml) when the
// author pushes or comments, or manually by a maintainer.
const prs = await github.paginate(github.rest.pulls.list, {
owner, repo, state: 'open', sort: 'updated', direction: 'desc', per_page: 100,
});
let examined = 0;
for (const prSummary of prs) {
if (examined >= maxPrs) {
core.info(`Reached MAX_PRS (${maxPrs}); stopping.`);
break;
}
examined++;
if (prSummary.labels.some(l => l.name === awaitingLabel)) {
continue;
}
// A reviewer requested changes and the author hasn't responded.
// (Merge conflicts are intentionally not labelled here: maintainers
// usually know the reason and clean these up as part of the merge
// process, or leave a review comment asking the author to resolve
// it, which is then caught by the check below.)
const { data: pr } = await github.rest.pulls.get({
owner, repo, pull_number: prSummary.number,
});
const reviews = await github.paginate(github.rest.pulls.listReviews, {
owner, repo, pull_number: pr.number, per_page: 100,
});
// Note: the REST API reports review states in UPPERCASE
// ("CHANGES_REQUESTED"), unlike webhook payloads which are lowercase.
const decisive = reviews.filter(r =>
r.state === 'CHANGES_REQUESTED' || r.state === 'APPROVED'
);
const latest = decisive[decisive.length - 1];
if (!latest || latest.state !== 'CHANGES_REQUESTED') {
continue;
}
// If the author has pushed since the review, treat that as a response
// (same rule as the event-driven removal workflow).
if (latest.commit_id !== pr.head.sha) {
continue;
}
// If the author has commented since the review, that also counts
// as a response.
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: pr.number, since: latest.submitted_at, per_page: 100,
});
const authorResponded = comments.some(c =>
(c.user?.login || '') === pr.user.login
);
if (authorResponded) {
continue;
}
await github.rest.issues.addLabels({
owner, repo, issue_number: pr.number, labels: [awaitingLabel],
});
core.info(`#${pr.number}: added "${awaitingLabel}" (changes requested, no response).`);
}
core.info(`Scan complete; examined ${examined} PR(s).`);