From 0c18cb9c634e76565d7b3373020e916f4dfbf957 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 9 Sep 2026 13:12:54 -0400 Subject: [PATCH] fix(rss-notifier): guard duplicate issues --- .github/workflows/rss-notifier.yml | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/.github/workflows/rss-notifier.yml b/.github/workflows/rss-notifier.yml index 1563b85..68f2c47 100644 --- a/.github/workflows/rss-notifier.yml +++ b/.github/workflows/rss-notifier.yml @@ -47,6 +47,64 @@ jobs: return 'false'; } + // Independently enforce idempotency before publishing. The RSS monitor's + // upstream action can occasionally create an issue for an item that it + // has already seen, so the issue URL is the durable publication key. + const blogUrl = issue.data.body?.trim(); + if (!blogUrl) { + console.log('Issue has no blog URL'); + return 'false'; + } + + const existingBlogIssues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'all', + labels: 'blog', + per_page: 1 + }); + if (existingBlogIssues.data.length === 0) { + throw new Error( + 'GitHub API returned no blog issues even though the current issue has the blog label.' + ); + } + + const escapedBlogUrl = blogUrl + .replaceAll('\\', '\\\\') + .replaceAll('"', '\\"'); + const matches = await github.rest.search.issuesAndPullRequests({ + q: `repo:${context.repo.owner}/${context.repo.repo} is:issue label:blog in:body "${escapedBlogUrl}"`, + per_page: 100 + }); + const duplicate = matches.data.items + .filter(candidate => candidate.number !== issue.data.number) + .filter(candidate => candidate.body?.trim() === blogUrl) + .sort((left, right) => left.number - right.number)[0]; + + if (duplicate) { + console.log(`Blog URL was already published by issue #${duplicate.number}`); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.data.number, + body: `Skipping publication because this blog URL was already processed in #${duplicate.number}.` + }); + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.data.number, + state: 'closed', + state_reason: 'not_planned' + }); + await github.rest.issues.lock({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.data.number, + lock_reason: 'resolved' + }); + return 'false'; + } + return 'true'; moonlight-conditions: