Skip to content

🌱 Sync workflows from kubestellar/infra #23969

🌱 Sync workflows from kubestellar/infra

🌱 Sync workflows from kubestellar/infra #23969

name: Hold Issue Guard
on:
pull_request_target:
types: [opened, edited, synchronize, labeled, unlabeled]
issues:
types: [labeled, unlabeled]
permissions:
issues: read
pull-requests: read
jobs:
check-hold-issues:
runs-on: ubuntu-latest
timeout-minutes: 5
# Run on PR events always; on issue events only when the hold label is involved
if: >-
(github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'issues' && github.event.label.name == 'hold')
steps:
- name: Check for hold-labeled referenced issues
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
// Matches: Fixes #123, Closes: #123, Resolves #123, fix #45, close: #9, etc.
const CLOSING_PATTERN = /(?:fix(?:es)?|close[sd]?|resolve[sd]?):?\s+#(\d+)/gi;
// For issue events, find open PRs that reference this issue
if (context.eventName === 'issues') {
const issueNumber = context.payload.issue.number;
const { data: pulls } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
});
const hasHold = context.payload.issue.labels.some(l => l.name === 'hold');
if (!hasHold) {
core.info(`Issue #${issueNumber} no longer has hold label. No action needed.`);
return;
}
for (const pr of pulls) {
const body = pr.body || '';
let match;
while ((match = CLOSING_PATTERN.exec(body)) !== null) {
if (parseInt(match[1], 10) === issueNumber) {
core.setFailed(
`PR #${pr.number} references hold-labeled issue #${issueNumber}.\n` +
`Remove the closing keyword or remove the hold label first.`
);
return;
}
}
}
core.info(`No open PRs reference hold-labeled issue #${issueNumber}.`);
return;
}
// For pull_request_target events
const body = context.payload.pull_request.body || '';
let match;
const referencedIssues = [];
while ((match = CLOSING_PATTERN.exec(body)) !== null) {
referencedIssues.push(parseInt(match[1], 10));
}
if (referencedIssues.length === 0) {
core.info('No closing keywords found in PR body.');
return;
}
core.info(`Found referenced issues: ${referencedIssues.join(', ')}`);
const holdIssues = [];
for (const issueNumber of referencedIssues) {
try {
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
});
const hasHold = issue.labels.some(l => l.name === 'hold');
if (hasHold) {
holdIssues.push(`#${issueNumber} (${issue.title})`);
}
} catch (e) {
// Treat API errors (404, 403, transient) as non-blocking
core.warning(`Could not fetch issue #${issueNumber}: ${e.message}. Skipping.`);
}
}
if (holdIssues.length > 0) {
core.setFailed(
`This PR references hold-labeled issues that must not be auto-closed:\n` +
holdIssues.map(i => ` - ${i}`).join('\n') +
`\n\nRemove the "Fixes/Closes" keyword for these issues, or remove the hold label first.`
);
} else {
core.info('No hold-labeled issues referenced. OK.');
}