fix(ops): guard against duplicate same-fix pull requests - #80
Conversation
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
|
Warning Review limit reached
Next review available in: 42 minutes Limit details: You’ve used the included review currently available. Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (5)
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f11f9d22f3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const canonical = canonicalNumber(report.matches) | ||
| lines.push(`Same-fix duplicate open PRs detected for #${report.prNumber} (${report.currentTitle}).`) | ||
| lines.push(`Canonical: #${canonical}. Close this PR and keep the canonical one, or consolidate the fix there.`) |
There was a problem hiding this comment.
Include the current PR when choosing the canonical PR
When the earlier PR in a duplicate pair is synchronized, reopened, or marked ready, matches contains only the later PR because the current PR is excluded during detection. Computing the canonical number from that list therefore identifies the later PR as canonical and tells maintainers to close the actual earliest PR; for example, rerunning identical PR #10 after #11 exists chooses #11. Compare the current PR's created_at alongside the matches before issuing the close instruction.
Useful? React with 👍 / 👎.
| if (report.matches.length > 0 && args.comment) { | ||
| await upsertComment(api, owner, repo, args.pr, formatReport(report, args.repo)) | ||
| } |
There was a problem hiding this comment.
Remove stale guard comments after duplicates disappear
If a PR was previously flagged and a subsequent synchronization changes its patch so that no duplicate remains, this condition skips upsertComment entirely. The check turns green, but the old marker comment remains and still instructs maintainers to close the PR, contradicting the current result. The no-match path should remove any existing marker comment or replace it with a resolved report.
Useful? React with 👍 / 👎.
| ```bash | ||
| node scripts/check-pr-duplicates.mjs --pr 0 --repo nish3451/tinystudio-in --no-fail | ||
| ``` |
There was a problem hiding this comment.
Do not advertise an unsupported pre-PR invocation
This documented command always exits with --pr NUMBER is required because argument parsing explicitly rejects --pr 0; moreover, detection expects that number to identify an open PR returned by GitHub. Consequently the advertised local-before-opening check cannot inspect anything. Document a real existing PR number, or implement a mode that compares the local diff without requiring an open PR.
Useful? React with 👍 / 👎.
| pr-number: | ||
| description: "PR number to check (defaults to the PR that triggered the run)" | ||
| required: false | ||
| type: string |
There was a problem hiding this comment.
Require a PR number for manual dispatches
A workflow_dispatch run has no pull_request payload, so when this optional input is omitted the PR_NUMBER expression resolves empty and the script immediately rejects it as missing. The description's claimed fallback only exists for pull-request-triggered runs, not manual dispatches; make this input required or provide a valid manual-run default.
Useful? React with 👍 / 👎.
The fleet keeps dispatching the same finding to multiple lanes, producing duplicate same-fix PR pairs: #36/#44 and #55/#56 are byte-identical or near-identical patches of the operator export --help fix, #39/#49 the offername article fix, #40/#52 the recording exporter --help fix, plus #38/#48/#51, #30/#61/#72, #45/#64, #42/#60, #43/#54 and #46/#74. Add a PR Duplicate Guard: - scripts/check-pr-duplicates.mjs compares a PR's diff against every other open PR (shared changed-file coverage >= 0.8 and patch similarity >= 0.5). Calibrated on all 79 open PRs on 2026-08-11: every pair above the thresholds was a genuine duplicate-cluster member, zero false positives. - .github/workflows/pr-duplicate-guard.yml runs it on every PR event and posts one marker comment naming the duplicate(s) and the canonical PR; the check fails loudly when a duplicate is found. Informational, not required. - scripts/test-pr-duplicates.mjs covers parsing, similarity, detection, and comment upsert with an injected API; wired into npm ci and npm test.
f11f9d2 to
2091c7a
Compare
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2091c7a6e1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const linesA = shared.flatMap(path => a.get(path)) | ||
| const linesB = shared.flatMap(path => b.get(path)) | ||
| return {coverage, similarity: ratio(linesA, linesB), sharedFiles: shared} |
There was a problem hiding this comment.
Compare only changed content when scoring similarity
Because the ratio includes diff --git, ---, +++, and hunk headers, two unrelated short edits to the same file can exceed the 0.5 threshold. For example, distinct one-line replacements at the same hunk location score 0.625 even with different index and changed-content lines, so the workflow fails and recommends closing a legitimate PR. Strip diff scaffolding or weight actual additions/removals before applying the threshold.
Useful? React with 👍 / 👎.
| for (const pr of prs) { | ||
| if (pr.number === prNumber) continue | ||
| const other = patches.get(pr.number) |
There was a problem hiding this comment.
Restrict duplicate comparisons to the current base branch
The open-pulls response includes PRs targeting every base branch, but this loop compares them without checking pr.base.ref. When an identical patch is intentionally proposed to both the main and a release branch, each is required independently, yet this guard labels them duplicates and instructs maintainers to close one. Filter candidates to the current PR's base branch before comparing patches.
Useful? React with 👍 / 👎.
| const list = await api(`${BASE_URL}/repos/${owner}/${repo}/issues/${prNumber}/comments`) | ||
| const comments = await list.json() |
There was a problem hiding this comment.
Paginate marker-comment lookup before posting
The issue-comments endpoint is paginated, but this request reads only its first page. On a PR with more than one page of comments, a marker outside that page is not deleted and every duplicate-check rerun posts another marker, violating the function's promise to keep exactly one authoritative report. Request all pages, or search with explicit pagination, before posting the replacement.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f45ab27d5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| with: | ||
| node-version: 20 | ||
| - name: Detect same-fix duplicate open PRs | ||
| run: node scripts/check-pr-duplicates.mjs --pr "$PR_NUMBER" --comment |
There was a problem hiding this comment.
Avoid executing PR-controlled code with a write token
When a same-repository PR modifies scripts/check-pr-duplicates.mjs, the checkout loads that PR's version and this step executes it with the write-capable GITHUB_TOKEN from the job environment. Unreviewed PR code can therefore perform arbitrary pull-request API mutations; execute an immutable copy of the script from the base revision, or separate detection from a trusted privileged commenting job.
Useful? React with 👍 / 👎.
| const coverage = shared.length / minFiles | ||
| const linesA = shared.flatMap(path => a.get(path)) | ||
| const linesB = shared.flatMap(path => b.get(path)) | ||
| return {coverage, similarity: ratio(linesA, linesB), sharedFiles: shared} |
There was a problem hiding this comment.
Reject one-sided changed-file coverage matches
When the current PR contains an earlier PR's one-file patch plus any number of unrelated files, dividing by the smaller file count still gives 100% coverage, while similarity ignores every non-shared file. The report then marks the broader PR as a duplicate and tells maintainers to close it, potentially discarding its additional work; require coverage in both directions or otherwise account for the unmatched files before issuing that instruction.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 57f654a963
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const pr of prs) { | ||
| const res = await api(`${BASE_URL}/repos/${owner}/${repo}/pulls/${pr.number}`, {accept: "application/vnd.github.v3.diff"}) | ||
| patches.set(pr.number, parseDiff(await res.text())) |
There was a problem hiding this comment.
Avoid fetching every open PR diff on each event
With the 79 open PRs stated in this commit, this loop makes roughly 80 REST requests per invocation. GitHub limits unauthenticated REST clients to 60 requests/hour and GITHUB_TOKEN to 1,000 requests/hour per repository, so the documented tokenless local check cannot complete even once, while around 13 PR events within an hour can exhaust the workflow's shared quota and make clean PR checks fail with an API error. Batch, cache, or prefilter candidates before downloading every diff. See GitHub's primary rate-limit documentation and GITHUB_TOKEN limit.
Useful? React with 👍 / 👎.
# Conflicts: # package.json
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2db89fd44d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| with: | ||
| node-version: 20 | ||
| - name: Detect same-fix duplicate open PRs | ||
| run: node scripts/check-pr-duplicates.mjs --pr "$PR_NUMBER" --comment |
There was a problem hiding this comment.
Preserve duplicate reports for fork pull requests
In this pull_request workflow, when a fork-authored or Dependabot PR is detected as a duplicate, the standard GITHUB_TOKEN is read-only despite the requested YAML permission, so upsertComment fails with 403 while deleting or posting the marker comment. The check still turns red, but the promised report never appears on the PR; GitHub documents the fork token restriction under workflows in forked repositories. Route commenting through a trusted privileged workflow while keeping PR-controlled detection unprivileged.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4fbdc345a9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (comment.body.includes(COMMENT_MARKER)) { | ||
| await api(`${BASE_URL}/repos/${owner}/${repo}/issues/comments/${comment.id}`, {method: "DELETE"}) |
There was a problem hiding this comment.
Restrict marker deletion to bot-authored comments
When a contributor quotes or manually includes <!-- pr-duplicate-guard --> in a discussion comment, the next duplicate-guard run deletes that comment because this check does not verify the comment author. Since the workflow runs with pull-requests: write, only comments previously created by the guard bot should be eligible for replacement.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8120c4d1d6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| bestLen = len | ||
| bestA = i - len + 1 | ||
| bestB = j - len + 1 | ||
| } else if (len === bestLen && (bestB === -1 || j < bestB)) { |
There was a problem hiding this comment.
Make patch similarity independent of PR direction
When repeated lines create equally long candidate matches, this tie-break chooses the smaller b position without considering the a position, making ratio asymmetric. For example, ratio(["A","B"], ["B","A","C","B"]) returns 0.33 while reversing the arguments returns 0.67; because detectDuplicates always places the currently checked PR first, the same pair can fall on opposite sides of the default 0.5 threshold depending on which PR receives an event. Use a symmetric score or deterministic tie-breaking that produces the same duplicate decision in both directions.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e3ea807c7b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, ready_for_review] |
There was a problem hiding this comment.
Recheck surviving matches when a peer PR closes
In the inspected pr-duplicate-guard.yml workflow, closing or merging a matched PR triggers none of these event types, so the remaining PR is never reevaluated. If the recommended canonical PR is closed without merging, the survivor retains both a failed check and a marker comment instructing maintainers to close it in favor of a PR that is no longer open; cleaning up only during later synchronizations does not help when its head remains unchanged. Add handling that reevaluates affected open peers when a PR closes, or periodically reconcile outstanding reports.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a2e190133
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const hits = bIndex.get(a[i]) ?? [] | ||
| const next = new Map() | ||
| for (const j of hits) { |
There was a problem hiding this comment.
Bound repeated-line similarity work
When two patches modify the same file with thousands of identical lines, this loop visits every occurrence in b for every matching line in a, making ratio quadratic and allocating a new map on each iteration. An identical 5,000-line input took about 16 seconds locally, while 10,000 lines exhausted the process before completing; because the workflow has no timeout and runs on a shared self-hosted runner, a generated or adversarial duplicate patch can monopolize or crash that runner. Cap the compared patch size or use a similarity implementation with bounded resource usage.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eb77414d0b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| - uses: actions/setup-node@v7 | ||
| with: |
There was a problem hiding this comment.
Pin privileged workflow actions to immutable commits
Unlike the existing repo-check workflow, these steps reference mutable major-version tags. If either tag is retargeted or its upstream release is compromised, replacement code runs on the self-hosted runner; checkout also receives the workflow's write-capable token and persists credentials for subsequent steps, enabling unauthorized PR mutations or runner compromise without any change to this repository. Pin both actions to reviewed full commit SHAs, as .github/workflows/codex-ci.yml already does.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 95228e19d8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await api(`${BASE_URL}/repos/${owner}/${repo}/issues/comments/${comment.id}`, {method: "DELETE"}) | ||
| } | ||
| } | ||
| await api(`${BASE_URL}/repos/${owner}/${repo}/issues/${prNumber}/comments`, {method: "POST", body}) |
There was a problem hiding this comment.
Wrap the comment text in the API body object
When a duplicate is found, the --comment path invoked by pr-duplicate-guard.yml passes the report string directly here, and makeApi serializes it as a JSON string. GitHub's Create an issue comment endpoint requires an object whose body property contains the comment text, so this request is rejected and the workflow never posts its duplicate report (and deletes the previous report first on reruns). Pass {body} as the request payload and update the fake API test to validate its shape.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d8c186deb3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let current = null | ||
| for (const line of String(text ?? "").split("\n")) { | ||
| if (line.startsWith("diff --git ")) { | ||
| const path = line.slice("diff --git a/".length).split(" b/")[0] |
There was a problem hiding this comment.
Parse diff paths without splitting inside filenames
When a PR touches a legal path containing the substring b/, such as foo b/one.txt, Git emits diff --git a/foo b/one.txt b/foo b/one.txt, and this split records the path as only foo. Multiple files under that prefix then overwrite the same map entry, corrupting changed-file coverage and patch similarity and potentially producing either missed duplicates or false reports. Parse the two Git path fields with quoting and separator rules instead of splitting at the first b/.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 99030e83ca
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let current = null | ||
| for (const line of String(text ?? "").split("\n")) { | ||
| if (line.startsWith("diff --git ")) { | ||
| const path = line.slice("diff --git a/".length).split(" b/")[0] |
There was a problem hiding this comment.
Include rename destinations in changed-file identity
When two PRs rename the same source file to different destinations, this keys both patches only by the old a/ path. Pure renames such as old.txt -> one.txt and old.txt -> two.txt consequently receive 100% file coverage and 60% similarity from the shared rename metadata, exceeding both defaults even though they propose incompatible destinations; the workflow then labels one a same-fix duplicate and recommends closing it. Parse and compare both source and destination paths so differently targeted renames cannot match solely through their old name.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
Resolve package.json conflict by taking main's ci/test chains (which added test-operator-check-strictness, test-deploy-public-site-workflow and test-pages-release since this branch was cut) and re-inserting this PR's test-pr-duplicates.mjs before the node --check sweep.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
package.json conflict only after the latest main merges: take main ci/test chains and re-insert this PR own check. Guard re-run against current main.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
…main by PR #80 (duplicate guard) (#234) The duplicate-PR guard (PR #80, merged 2026-08-19) is now live on main, preventing the same-fix duplication pattern at the CI level. The named duplicate pairs (#36/#44, #39/#49, #40/#52) are functionally superseded: their underlying fixes are on main via PRs #135, #145, and #178. The prior 2026-08-15 lane run had concluded the guard existed but was stuck unmerged; on 2026-08-19 it landed and the item is resolved at the root-cause level. Verification-only run (no product or test files touched): - PR #80 source commit 2091c7a and merge commit 0a9909b are ancestors of origin/main - scripts/test-pr-duplicates.mjs → 'test-pr-duplicates: ok' (exit 0) - Superseding PRs #135 (d4f3ef4), #145 (fc44b42), #178 (77f6922) all on main - Guard workflow runs on pull_request events; not a required status, so existing work never blocks Co-authored-by: minimax-vps <minimax-vps@nish3451.dev>
Why
Duplicate same-fix PR pairs keep appearing because the same finding is dispatched to multiple lanes:
--help(byte-identical patches for fix(ops): operator export scripts honor --help and refuse out-of-repo output paths #36/fix(ops): growth/ops export scripts honor --help without overwriting tracked artifacts #44)--helpToday these pile up silently; a human (or Grok review) has to notice by hand.
What
scripts/check-pr-duplicates.mjs— compares a PR's diff against every other open PR: shared changed-file coverage>= 0.8and patch similarity>= 0.5on the shared files ⇒ same-fix duplicate. Calibrated against all 79 open PRs on 2026-08-11: every pair above the thresholds is a genuine duplicate-cluster member (18 pairs), zero false positives; unrelated pairs (e.g. fix(ops): stop tracked live metrics surface from silently reporting a zero pipeline #73 vs fix(ops): stop tracked 11/10 proof-run brief from silently reporting a zero pipeline #45) stay below..github/workflows/pr-duplicate-guard.yml— runs on every PR event, fails the check loudly and posts one marker comment naming the duplicate(s) and the canonical PR. Informational (not a required status), so it never blocks legitimate work — it makes duplication visible the moment it happens.scripts/test-pr-duplicates.mjs— unit tests (parsing, similarity, detection, comment upsert) with an injected API; wired intonpm ciandnpm test.Verified
npm run ci— 19 checks + test-pr-duplicates, 0 failures.Outcome for the fleet
The next time a lane opens a PR for an already-in-flight finding, the guard fails red with the canonical PR number and a close/consolidate instruction, so the orchestrator can close the duplicate instead of letting it sit silently.