From 32006490305217d428e442ee3e4c21fcf648ca2b Mon Sep 17 00:00:00 2001 From: Fernando Lins <1887601+fernandolins@users.noreply.github.com> Date: Thu, 28 May 2026 12:45:59 -0300 Subject: [PATCH 1/2] ci: notify author when PR is missing linked issue --- .github/workflows/check-linked-issue.yml | 89 ++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 .github/workflows/check-linked-issue.yml diff --git a/.github/workflows/check-linked-issue.yml b/.github/workflows/check-linked-issue.yml new file mode 100644 index 0000000000..8678c97eff --- /dev/null +++ b/.github/workflows/check-linked-issue.yml @@ -0,0 +1,89 @@ +name: Check Linked Issue + +on: + pull_request: + types: [opened, edited] + +jobs: + check-linked-issue: + runs-on: ubuntu-latest + permissions: + pull-requests: write + issues: write + + steps: + - name: Check for linked issue + id: check + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + const title = pr.title || ''; + const body = pr.body || ''; + const author = pr.user.login; + const labels = pr.labels.map(l => l.name); + + const isExempt = + /hotfix/i.test(title) || + author === 'dependabot[bot]' || + labels.includes('dependencies'); + + if (isExempt) { + core.setOutput('exempt', 'true'); + core.setOutput('has_linked_issue', 'true'); + return; + } + core.setOutput('exempt', 'false'); + + const linkedIssuePattern = /\b(closes|fixes|resolves|close|fix|resolve)\s+#\d+/i; + const hasLinkedIssue = linkedIssuePattern.test(body); + core.setOutput('has_linked_issue', hasLinkedIssue); + + - name: Apply missing-issue label and comment + if: steps.check.outputs.has_linked_issue == 'false' + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const issue_number = context.payload.pull_request.number; + + // Apply label + await github.rest.issues.addLabels({ + owner, + repo, + issue_number, + labels: ['missing-issue'] + }); + + // Post comment + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body: `Thanks for the PR! To ensure we're solving the right problems, we require all PRs to be linked to an open issue. Please open one describing the problem this PR addresses so we can discuss the implementation there first.\n\nYou have 4 days to link an issue before this PR is automatically closed, it can be reopened at any time after that. Looking forward to the discussion!\n\n> To link an issue, edit the PR description and add a line like \`Closes #123\`.` + }); + + - name: Remove missing-issue label if issue is now linked + if: steps.check.outputs.has_linked_issue == 'true' + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const issue_number = context.payload.pull_request.number; + + const labels = await github.rest.issues.listLabelsOnIssue({ + owner, + repo, + issue_number + }); + + const hasMissingLabel = labels.data.some(l => l.name === 'missing-issue'); + + if (hasMissingLabel) { + await github.rest.issues.removeLabel({ + owner, + repo, + issue_number, + name: 'missing-issue' + }); + } From e9ff893ee63d4fe11b868cd86eafcef4f7ad7408 Mon Sep 17 00:00:00 2001 From: Fernando Lins <1887601+fernandolins@users.noreply.github.com> Date: Thu, 28 May 2026 12:46:24 -0300 Subject: [PATCH 2/2] ci: auto-close PRs missing linked issue after 4 days --- .../workflows/auto-close-missing-issue.yml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/auto-close-missing-issue.yml diff --git a/.github/workflows/auto-close-missing-issue.yml b/.github/workflows/auto-close-missing-issue.yml new file mode 100644 index 0000000000..1b45290797 --- /dev/null +++ b/.github/workflows/auto-close-missing-issue.yml @@ -0,0 +1,67 @@ +name: Auto-close PRs without linked issue + +on: + schedule: + - cron: '0 0 * * *' # daily at midnight UTC + workflow_dispatch: + +jobs: + auto-close: + runs-on: ubuntu-latest + permissions: + pull-requests: write + issues: write + + steps: + - name: Close PRs missing linked issue for over 4 days + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const fourDaysAgo = new Date(Date.now() - 4 * 24 * 60 * 60 * 1000); + + const prs = await github.paginate(github.rest.issues.listForRepo, { + owner, + repo, + state: 'open', + labels: 'missing-issue', + }); + + for (const pr of prs) { + if (!pr.pull_request) continue; + + const isExempt = + /hotfix/i.test(pr.title) || + pr.user.login === 'dependabot[bot]' || + pr.labels.some(l => l.name === 'dependencies'); + + if (isExempt) continue; + + // Find when the missing-issue label was applied + const events = await github.paginate(github.rest.issues.listEventsForTimeline, { + owner, + repo, + issue_number: pr.number, + }); + + const labelEvent = events + .filter(e => e.event === 'labeled' && e.label?.name === 'missing-issue') + .pop(); // most recent application + + if (!labelEvent) continue; + if (new Date(labelEvent.created_at) > fourDaysAgo) continue; + + await github.rest.pulls.update({ + owner, + repo, + pull_number: pr.number, + state: 'closed', + }); + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: pr.number, + body: `This PR has been automatically closed because no linked issue was provided within 4 days. Feel free to reopen it after linking an issue, just edit the PR description and add a line like \`Closes #123\`.`, + }); + } \ No newline at end of file