Skip to content

feat(ci): flag possible same-issue PR overlap on open - #8061

Draft
timwukp wants to merge 5 commits into
kirodotdev:mainfrom
timwukp:feat/contribution-duplicate-detection
Draft

feat(ci): flag possible same-issue PR overlap on open#8061
timwukp wants to merge 5 commits into
kirodotdev:mainfrom
timwukp:feat/contribution-duplicate-detection

Conversation

@timwukp

@timwukp timwukp commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Important

DRAFT — scope reset pending a maintainer decision on #8046.

Current head 68fc008a is not a merge candidate. It is conflicted, five commits / 304 upstream commits behind, and still contains proposal 4 even though that proposal was withdrawn after #7982 was found to have already addressed the stated mechanism. It also contains on-merge close/attribution behavior whose authority exceeds the evidence available to an overlap detector.

Please do not review the current diff. The intended replacement contract is recorded below; the original scope is preserved under Superseded original scope for the public timeline.

Problem / Motivation

Contributors and agents can begin work on an issue while an earlier open PR already covers overlapping files. The issue cross-reference exists, but the later author may not see it before duplicated implementation, CI, and review effort accumulates.

Why it matters

The repository loses contributor time and reviewer capacity, and whichever PR reaches the queue first can merge while the other remains open. This is a coordination failure, not evidence of misconduct or ownership.

What changed

No final implementation is proposed at the current head. The current revision is being held only while maintainers decide this narrowed, advisory-only contract:

  • On PR open/reopen/edit/synchronize, compare OPEN PR metadata.
  • A candidate requires both a shared closing issue and at least one shared changed file.
  • Upsert one marker-keyed “possible overlap” comment naming the earlier PR, shared issue, and shared files, and asking authors to compare scope and coordinate.
  • Update or remove the same comment when overlap changes.
  • Never block, label, close, rank, or attribute either PR.
  • Never check out or execute fork code; treat all PR metadata as untrusted data.
  • A detector/API failure must not make an unrelated contributor PR unmergeable.

If maintainers accept this contract on #8046, this branch will be rebuilt from current main; the fork-review healer and all on-merge close/attribution behavior will be removed, and the result will be squashed to one commit. If maintainers decline it, this PR will close without further implementation work.

Tests

Pending the scope decision. The replacement must prove at least:

  • same issue + shared files warns;
  • same issue without shared files does not warn;
  • shared files without a shared issue do not warn;
  • self and closed PRs never match;
  • one marker comment is updated rather than duplicated;
  • stale overlap removes/resolves the warning;
  • no fork code is checked out or executed;
  • detector/API failure does not block readiness.

Manual verification

Pending maintainer acceptance and branch reconstruction.

Why no screenshot: this is CI/process automation with no rendered UI change.

Pattern harvest

Rule candidate: When automation can detect possible duplicate work but cannot prove intent or equivalence, prefer an early, idempotent advisory over auto-closing or auto-attributing contributions.

Related: #8046

Superseded original scope at 68fc008 (preserved for the timeline)

Implements the four process/tooling proposals from issue #8046. This is forward-looking automation, not credit adjudication. No misconduct is alleged by the reporter, and Case B (#7852/#7898) was retracted, so no action is taken on it.

Proposal 4 (the plain bug): heal never-fired fork-review lanes

GitHub does not guarantee workflow_run delivery, so a fork PR can have CI conclude success yet never start any Stage-2 fork reviewer. With no review check-run and no future event to create one, pr-readiness.yml freezes that PR at pending forever, so it cannot merge and is eventually superseded (the #7553 case).

  • New .github/workflows/fork-review-heal.yml: a scheduled, idempotent, self-terminating sweep that finds OPEN fork PRs whose CI succeeded but whose fork-review check-runs never posted, and re-dispatches only the missing lanes.
  • Added a workflow_dispatch(head_sha) entrypoint to all five fork-*-review.yml reviewers, reusing their existing open-PR-by-head-SHA resolver. Fork code is never checked out or executed; a PR whose CI has not passed is left pending by design, never force-passed. The maintainer action_required and undelivered-dispatch boundaries are documented, not worked around.

Proposals 2 and 1: duplicate / overlap detection

  • New scripts/detect_pr_overlap.py: a pure, network-free, unit-tested engine. Overlap = same referenced issue (GraphQL closingIssuesReferences union a Fixes/Closes/Resolves #N body fallback) AND non-empty changed-file intersection; never self-matches; OPEN candidates only. Candidate set fetched in a single gh pr list call.
  • New .github/workflows/pr-duplicate-detect.yml:
    • On open (proposal 2): upserts one marker-keyed advisory comment naming overlapping earlier open PRs and the shared files; removes it when a revision no longer overlaps. Never blocks merge.
    • On merge (proposal 1): closes each strongly overlapping open same-issue PR (exact changed-file match or >=50% file coverage) with a pointer, and records co-authorship in a marker comment on the merged PR. A weak single-file overlap is left open. A no-auto-close-duplicate label opts a PR out of auto-close (pointer only).
    • Co-authored-by feasibility boundary is documented: a workflow runs after the squash-merge commit is composed from the PR title and cannot rewrite it to inject the trailer, so the durable recording comment is the placeable attribution.

Proposal 3: docs

  • CONTRIBUTING.md binds the “already on it” rule to automated/agent contributors and explains that an open PR referencing the same issue is the machine-readable signal the on-open lane surfaces.
  • docs/ci/ci-and-reviews.md documents both new mechanisms.

Testing

Full pytest is deferred to CI because the authoring sandbox is network-restricted (PyPI blocked; pytest/PyYAML/yamllint/actionlint uninstallable). In-sandbox substitute all green: bash -n on every new/edited workflow run-script; engine self-test (20 checks); two behavioural drivers executing the real extracted steps against gh stubs (fork-review-heal 11 checks, pr-duplicate-detect 17 checks) covering dispatch/comment/close side-effects, idempotency, the strong-overlap gate, and the label opt-out; py_compile; docs lint (257 files). PyYAML+pytest tests are committed for CI (test/test_fork_review_heal.py, test/test_pr_duplicate_detect.py).

Needs a repo admin at merge time (not code)

  • Create the no-auto-close-duplicate label if it should exist before the on-merge lane runs (the workflow only reads it).
  • Confirm the pinned action SHAs resolve on a networked runner (the authoring host had no network).

The revision declared #8046 as its closing target.

…eeze

A fork PR's Stage-2 AI reviewers start only on CI's workflow_run:completed
event, which GitHub drops silently under load. When that event is dropped
after CI passes, no fork-review check-run is ever posted, pr-readiness reads
the lane as not-started/only-skipped and counts it pending, and no future
event can recompute it -- so the fork PR is frozen pending forever and is
eventually superseded.

Add fork-review-heal.yml: a scheduled sweep that, for an open fork PR whose
latest CI run for the head SHA concluded success, re-dispatches only the
review lanes that never fired via each reviewer's new workflow_dispatch
entrypoint keyed to the head SHA. Each reviewer resolves its own PR from that
SHA with the existing open-PR-by-head-SHA resolver, so no new trust surface is
added and fork code is never checked out or executed. A lane is missing only
when it has no in-progress or completed non-skipped check-run, and reviewers
open an in-progress check-run immediately, so the sweep is self-terminating
and bounded by a dispatch cap. A fork PR whose CI has not passed stays pending
by design, attributed to CI; the awaiting-approval boundary stays
blocking-but-attributed and is never auto-cleared.
Add a shared overlap engine (scripts/detect_pr_overlap.py) and the
pr-duplicate-detect.yml workflow. On open it upserts one advisory comment
naming earlier open PRs that reference the same issue and touch intersecting
files; on merge it points at and closes superseded open same-issue PRs and
records co-authorship where a workflow can place it. Flagging requires both a
shared issue and a non-empty changed-file intersection, never matches a PR
against itself, and never blocks merge. Document the Co-authored-by boundary
(a workflow cannot rewrite the composed squash-merge commit) in
docs/ci/ci-and-reviews.md.
…overlap

- Materialize the open-PR candidate set in a single gh pr list call instead of
  a pr view plus GraphQL call per open PR, and lower the default --limit.
- Close a superseded open PR on merge only on a strong overlap (exact set match
  or shared files covering at least half the merged PR's files); leave weaker
  single-file overlaps to the on-open advisory.
- Add a no-auto-close-duplicate label opt-out that points at the merge but keeps
  the close under human control, preserving pointer-before-close ordering.
The on-merge co-authorship note previously listed every strongly-
overlapping PR under "now closed as superseded", including PRs carrying
the no-auto-close-duplicate label that were deliberately left open. The
note claimed a close that did not happen for opted-out PRs.

Track closed and left-open PR numbers separately and word each part of
the note accurately: closed PRs under "Closed as superseded" and
opted-out PRs under "Left open". The overlap is still recorded for
attribution either way. Pointer-before-close ordering, the strong-
overlap gate, the label opt-out mechanics, and the marker-keyed
idempotent upsert are unchanged.

Tests assert the note reports a real close for a closed PR and never
claims a close for an opted-out PR. Docs updated to match.
@timwukp
timwukp requested a review from a team as a code owner September 3, 2026 02:26
@timwukp
timwukp requested a review from patrigao September 3, 2026 02:26
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention merge conflict Branch has merge conflicts with its base — author must resolve before merge labels Sep 3, 2026
@timwukp
timwukp marked this pull request as draft September 5, 2026 07:20
@timwukp timwukp changed the title feat: detect duplicate same-issue PRs and heal never-fired fork reviews feat(ci): flag possible same-issue PR overlap on open Sep 5, 2026
@timwukp

timwukp commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Scope reset — this revision is not a merge candidate

I am converting this PR to draft and pausing implementation pending a maintainer decision on #8046.

The current head (68fc008a) bundled four proposals. That is no longer accurate or proportional:

  • Proposal 4 (fork-review healing) is withdrawn: fix(ci): give the same-repo review lanes a distinct check name on fork PRs #7982 had already addressed the two-publisher mechanism, and the issue's original explanation of skipped readiness was wrong.
  • Automatically closing another contributor's PR or recording co-authorship from overlap evidence is a stronger governance action than the detector can justify.
  • The branch is now 304 commits behind main, conflicted, and has five commits. Rebasing this superseded 2,497-line revision would preserve the wrong scope.

I posted the narrowed design contract on #8046. The proposed replacement is on-open advisory only: same closing issue + shared files → one idempotent “possible overlap” comment asking authors to coordinate. It will never block, label, close, rank, or attribute either PR, and it will never execute fork code.

Please do not review the current diff. If maintainers accept the contract, I will rebuild this same PR from current main and squash it to one commit. If not, I will close it.

@bolichen97

Copy link
Copy Markdown
Collaborator

@timwukp Thanks for the unusually self-disclosing PR body. The audit agrees with your own read that this head is not a merge candidate, and it also confirms the idea itself is not superseded.

What landed on main since you opened this: #7982 removed the two-publisher/skipped-twin hazard you cited when you withdrew proposal 4, and #8503 staged the Stage-2 fork reviewers behind Fast Gate, so those lanes now trigger on workflow_run of "Fast Gate" instead of "CI". That makes .github/workflows/fork-review-heal.yml's actions/workflows/ci.yml/runs query and its "CI has not passed" gate wrong against current main.

What main still does not have: any same-issue overlap detection. scripts/detect_pr_overlap.py, .github/workflows/pr-duplicate-detect.yml and .github/workflows/fork-review-heal.yml are all absent, and .github/workflows/pr-readiness-sweep.yml only re-fires the readiness aggregator over existing check-runs, so it cannot produce a never-fired Stage-2 review. That gap is real and worth closing.

Our ask: keep this in draft until #8046 has a decided contract, then rebuild on current main rather than rebase. The branch is 663 commits behind and currently conflicting, and main rewrote these same lanes extensively, so a rebase would fight a very large residual diff. On rebuild, please narrow to the advisory-only on-open lane plus scripts/detect_pr_overlap.py, drop the withdrawn proposal 4 and the on-merge auto-close and co-author job, and coordinate the fork-lane edits with #9318: it keys each lane's check-run external_id on the triggering run id and attempt, which a workflow_dispatch entrypoint does not have. Both PRs also touch the same fork-review section of docs/ci/ci-and-reviews.md.

Posted from the 2026-09-08 open-PR relationship audit (read-only, one auditor per PR); reply here if any of this is wrong.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor) merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: action required A blocking check or review needs attention

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants