Skip to content

spec: alert maintainers about duplicate PRs (#10395)#10495

Open
lonexreb wants to merge 4 commits intowarpdotdev:masterfrom
lonexreb:spec/10395-duplicate-pr-alert
Open

spec: alert maintainers about duplicate PRs (#10395)#10495
lonexreb wants to merge 4 commits intowarpdotdev:masterfrom
lonexreb:spec/10395-duplicate-pr-alert

Conversation

@lonexreb
Copy link
Copy Markdown
Contributor

@lonexreb lonexreb commented May 8, 2026

Spec for #10395.

Summary

Detect candidate-duplicate PRs (same issue reference, file-overlap Jaccard, small-PR title similarity, or rapid-fire same-author signals) and surface a non-blocking "Possible duplicates" callout in the Warp Code Review pane. Emit a structured MaintainerAlert.DuplicatePr signal that team admins can route to a configured Slack webhook and/or Linear issue.

Scope

  • In: detection signals (issue ref, ≥50% file Jaccard for ≥3-file PRs, identical paths + title cosine ≥0.70 for 1–2 file PRs, rapid-fire same-author), in-product callout above the diff with one-click candidate navigation, per-pair dismissal stored as a structured PR comment, Slack/Linear external signal routing, user-level enable toggle, team-level destination config.
  • Out: auto-closing duplicates, server-side merge orchestration, cross-repo dedupe (V1.5), AST/semantic dedupe.

Notes

  • Spec-only PR. No code changes.
  • The linked issue references an internal Slack thread (warpdev.slack.com) for additional context.
  • Tag: ready-to-spec.

Test plan

  • Confirm detection signals match maintainer expectations
  • Confirm Slack/Linear destination contract is acceptable
  • Confirm dismissal-as-PR-comment is the right persistence layer (vs. server-side store)
  • Confirm telemetry event names align with existing code_review.* namespace

@cla-bot cla-bot Bot added the cla-signed label May 8, 2026
@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 8, 2026

@lonexreb

I'm starting a first review of this spec-only pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@github-actions github-actions Bot added the external-contributor Indicates that a PR has been opened by someone outside the Warp team. label May 8, 2026
Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This spec defines the duplicate-PR detection signals, UI callout, dismissal behavior, external maintainer signal, settings, acceptance criteria, tests, and telemetry. The core behavior is covered, but several requirements need tightening before implementation.

Concerns

  • PR-comment dismissal persistence needs an explicit trust model so untrusted comments cannot forge suppression markers.
  • Team Slack webhook storage and handling must be specified as secret material.
  • External Slack/Linear signals need persistent dedupe or cooldown beyond a single review session.
  • The relationship between a per-user toggle and team-level external alert routing is ambiguous.

Security

  • The spec introduces a stored Slack webhook URL and structured PR comments that affect alert behavior; both need explicit security controls before implementation.

Verdict

Found: 0 critical, 4 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Comment thread specs/GH10395/SPEC.md

### B4. Suppression

The callout exposes a "Mark as not duplicate" button per candidate. Clicking it records a dismissal as a structured PR comment with marker `<!-- warp-dup-dismiss pr=<candidate_pr_number> -->`. On subsequent opens, the dismissed candidate is filtered out for that (PR, candidate) pair only. Dismissals are bidirectional — dismissing on PR A also hides PR A from PR B's candidate list.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] [SECURITY] Persisting dismissals as PR comments needs a trust model: specify who can create the marker, which comments are honored (for example bot-signed or maintainer-authored only), and how forged or edited comments are ignored so contributors cannot suppress alerts.

Comment thread specs/GH10395/SPEC.md

Team-level (admin-managed; surfaced under team settings UI):

- `team.duplicate_alert.signal.slack_webhook` (string, default empty) — Slack incoming-webhook URL.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] [SECURITY] A Slack webhook URL is a secret; require encrypted storage, UI/API redaction, auditability or rotation, and exclusion from telemetry/logs before adding this team setting.

Comment thread specs/GH10395/SPEC.md Outdated
- `team.duplicate_alert.signal.slack_webhook` (string, default empty) — Slack incoming-webhook URL.
- `team.duplicate_alert.signal.linear_issue_template` (string, default empty) — Linear issue body template; supports `{pr_number}`, `{candidates}`, `{signals}` placeholders.

If `code.review.duplicate_alert.enabled` is `false`, neither the callout nor the signal fires for that user.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] The user-level toggle suppresses team-level signal emission, but the spec does not define whether external alerts are user-scoped client behavior or team-scoped routing. Clarify ownership so one user's setting cannot silently disable or duplicate a team admin's Slack/Linear alerts.

Comment thread specs/GH10395/SPEC.md Outdated
- A2. Two PRs touching ≥3 files with ≥50% Jaccard path-set overlap each show the other in their callout with signal label "X% file overlap".
- A3. A 1–2 file PR matches another only when path sets are identical AND title cosine ≥ 0.70.
- A4. A dismissed candidate stays hidden for that (PR, candidate) pair on subsequent opens, in both directions.
- A5. The `MaintainerAlert.DuplicatePr` signal fires at most once per (PR, candidate) pair per session; subsequent detections of the same pair do not re-fire.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] [SECURITY] External signals are only deduped per session, while detection reruns hourly and on reopen. Define persistent dedupe or cooldown per (PR, candidate, destination) so Slack/Linear routes cannot be spammed across sessions.

@lonexreb
Copy link
Copy Markdown
Contributor Author

lonexreb commented May 8, 2026

Round-1 fixes for oz-for-oss review: (1) PR-comment dismissal trust model — warp-dup-dismiss marker only respected from comments by OWNER/MEMBER/COLLABORATOR; external contributors' markers ignored; tests added; (2) Slack webhook treated as secret material — encrypted-at-rest, admin-only access, TLS-only transmission, log sanitization, rotation invalidates immediately; (3) persistent dedupe by (source_pr, candidate_pr, signal_type) tuple with 7-day TTL — survives sessions/restarts/different reviewers; (4) clarified user-toggle vs team-routing scopes — user toggle controls per-user UI callout; team-level signal fires per dedupe-tuple regardless of individual toggles.

@lonexreb
Copy link
Copy Markdown
Contributor Author

lonexreb commented May 8, 2026

/oz-review

@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 8, 2026

@lonexreb

I'm re-reviewing this spec-only pull request in response to a review request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This spec defines duplicate-PR detection, in-product callouts, dismissals, and team-routed Slack/Linear maintainer alerts. The overall structure is complete, but a few requirements are ambiguous or internally inconsistent enough to produce incorrect or unsafe behavior if implemented as written.

Concerns

  • The rapid-fire same-author signal does not define the diff overlap metric, while other sections say V1 should use path-only overlap.
  • The dismissal trust model uses author_association as a proxy for write access, but MEMBER does not guarantee repository write permission.
  • External-signal dedupe is keyed directionally even though the spec promises the cooldown applies when either PR in the pair is opened.

Security

  • Dismissal suppression must be gated on an actual repository permission check; otherwise lower-privileged org members could forge suppression markers and hide duplicate alerts.

Verdict

Found: 0 critical, 3 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Comment thread specs/GH10395/SPEC.md Outdated
1. **Same issue reference.** Another open PR in the same repo references the same issue via `Closes #N`, `Fixes #N`, or `Resolves #N` (case-insensitive) in its body.
2. **File-overlap.** For PRs touching ≥3 files, Jaccard similarity of changed-file path sets ≥ 0.50 with another open PR.
3. **Small-PR heuristic.** For PRs touching 1–2 files, all changed paths match another open PR AND title TF-IDF cosine similarity ≥ 0.70.
4. **Rapid-fire same author.** Same author submitting more than one open PR with ≥30% diff overlap within a 24-hour window. (Captures force-push-as-new-PR cases.)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] diff overlap is undefined and conflicts with the V1 path-only constraint/open question; specify whether this is path-set Jaccard, line-add/remove overlap, the denominator, and thresholds so tests and implementation match.

Comment thread specs/GH10395/SPEC.md Outdated
Server-side verification: the GitHub API exposes the comment author's permissions via the `author_association` field on each comment. Suppression is only applied when:

```
author_association ∈ { OWNER, MEMBER, COLLABORATOR }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] [SECURITY] MEMBER is not equivalent to repo write access on org-owned repos, so this can honor forged dismissals from org members without write permission; require a direct repository permission check (write/admin) before applying the marker.

Comment thread specs/GH10395/SPEC.md Outdated

### B-Dedup. Persistent dedupe / cooldown for external signals

External signals (Slack/Linear) are deduped by the tuple `(source_pr, candidate_pr, signal_type)` where `signal_type ∈ { same_issue_ref, file_overlap, small_pr_match, rapid_fire_same_author }`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] The dedupe key is directional, but the TTL text promises no re-fire when either PR in the pair is opened; normalize the pair key (for example, min/max PR numbers) or explicitly allow two directional alerts so external routing behavior is testable.

@lonexreb
Copy link
Copy Markdown
Contributor Author

lonexreb commented May 8, 2026

Addressed all 3 important concerns from the latest oz-for-oss review:

  1. Diff overlap definition (B1.4): Defined diff overlap for the rapid-fire same-author signal explicitly as path-set Jaccard (|paths(A) ∩ paths(B)| / |paths(A) ∪ paths(B)|) at threshold 0.30. V1 is path-only; line-level overlap is deferred to V1.5.
  2. Dismissal trust model (B4a, A8/A9, T-tests): Replaced the author_association-only check with an authoritative GET /repos/{owner}/{repo}/collaborators/{username}/permission call requiring permission ∈ { admin, maintain, write }. author_association is now only used as a fast-path negative filter. Added 10-minute per-(repo, user) cache. New test T_dismissal_trust_member_without_write_ignored covers the MEMBER-without-write case.
  3. External-signal dedupe (B-Dedup, A5): Re-keyed dedupe by canonical unordered pair ({pr_a, pr_b}, signal_type) so opening either PR in the pair hits the same key. Updated tests to assert bidirectional behavior.

Commit: ea02067

@lonexreb
Copy link
Copy Markdown
Contributor Author

lonexreb commented May 8, 2026

/oz-review

@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 8, 2026

@lonexreb

I'm re-reviewing this spec-only pull request in response to a review request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This spec defines duplicate-PR detection, Code Review pane surfacing, dismissal persistence, external Slack/Linear routing, cooldown behavior, permissions, telemetry, and tests.

Concerns

  • The user-level toggle contract is internally inconsistent: the API surface says it toggles both the callout and signal emission, while the later independence rules and acceptance criteria say external signals continue regardless of individual user settings.

Verdict

Found: 0 critical, 1 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Comment thread specs/GH10395/SPEC.md Outdated

User-level (Settings → Code Review → "Duplicate PR alerts"):

- `code.review.duplicate_alert.enabled` (bool, default `true`) — toggles both the in-product callout and signal emission for the current user.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] This contradicts lines 138-141 and A6, which say the user toggle only controls the in-product callout while team-level external signals still fire. Update this setting description so implementers do not wire the toggle to suppress Slack/Linear routing.

Resolve user-level toggle scope inconsistency. The API surface
description previously said the toggle controlled both callout and
signal emission, contradicting the independence rules and acceptance
criteria. The setting description now states it suppresses only the
per-user callout and has no effect on team-level external signals.
Added an explicit 'What it does NOT control' column to the scope
table and marked it authoritative.
@lonexreb
Copy link
Copy Markdown
Contributor Author

lonexreb commented May 8, 2026

Addressed the round-N review concern about the user-toggle inconsistency.

  • Rewrote the API-surface description of code.review.duplicate_alert.enabled to explicitly state it suppresses only the per-user callout and has no effect on team-level external signal emission.
  • Added a 'What it does NOT control' column to the scope table and marked the table authoritative for any apparent contradictions elsewhere.
  • Removed the stale 'previous wording superseded by' line — the contradiction it referenced is no longer present.

Independence rules and acceptance criteria (A6) are now consistent with the API description.

@lonexreb
Copy link
Copy Markdown
Contributor Author

lonexreb commented May 8, 2026

/oz-review

@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 8, 2026

@lonexreb

I'm re-reviewing this spec-only pull request in response to a review request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This spec defines duplicate-PR detection, an in-product callout, maintainer alert routing, dismissal persistence, and Slack/Linear destination handling. The high-level behavior is mostly covered, but several contract details are still ambiguous enough to produce inconsistent implementations.

Concerns

  • The maintainer-alert payload is aggregated while the cooldown model is keyed per candidate pair and signal type, leaving mixed deduped/new candidate runs undefined.
  • Bidirectional dismissal uses a one-sided PR comment marker, but the spec does not define where markers are scanned, how many are written, or how reversal works.
  • The repository's spec convention expects product and tech contracts in predictable files, while this PR adds only a combined SPEC.md.

Security

  • Slack webhook validation only requires HTTPS; the spec should either constrain destinations to Slack webhook hosts with SSRF-safe redirect/network handling or explicitly define arbitrary outbound webhook behavior and protections.

Verdict

Found: 0 critical, 3 important, 1 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Comment thread specs/GH10395/SPEC.md
@@ -0,0 +1,211 @@
# Alert Maintainers When There Are Duplicates Of A PR (GH-10395)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 [SUGGESTION] This repo's spec workflow typically expects separate product.md and tech.md files under specs/GH10395/; either split this combined SPEC.md or state that the combined file is intentional so downstream implementation/review workflows know which contract to consume.

Comment thread specs/GH10395/SPEC.md

### B3. Maintainer signal

When ≥1 candidate is detected for a PR, emit `MaintainerAlert.DuplicatePr { pr_number, candidate_pr_numbers, signal_types }`. Configured destinations are read from team-level settings; admins configure once. Supported destinations:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] The signal payload is aggregate, but B-Dedup keys cooldowns per candidate/signal tuple; define whether dedupe filters tuples before one aggregate emission or whether each tuple emits separately, otherwise Slack/Linear behavior is undefined for mixed new and already-deduped candidates.

Comment thread specs/GH10395/SPEC.md

### B4. Suppression

The callout exposes a "Mark as not duplicate" button per candidate. Clicking it records a dismissal as a structured PR comment with marker `<!-- warp-dup-dismiss pr=<candidate_pr_number> -->`. On subsequent opens, the dismissed candidate is filtered out for that (PR, candidate) pair only. Dismissals are bidirectional — dismissing on PR A also hides PR A from PR B's candidate list.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] The dismissal marker only stores the candidate number on the current PR, while bidirectional suppression and later both sides delete semantics require knowing which PR threads are scanned and cleared; specify one-marker vs two-marker storage and deletion rules.

Comment thread specs/GH10395/SPEC.md
- Admin reads of the webhook (e.g., to copy it for rotation) are themselves an audited event in telemetry: `team.duplicate_alert.webhook_revealed { admin_user_id }`.

**Transmission.**
- The webhook is invoked over **TLS only**. Plain HTTP webhook URLs are rejected at save time with a validation error.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] [SECURITY] HTTPS-only does not ensure this is a Slack webhook or prevent server-side SSRF; require allowed Slack webhook hosts, reject redirects/private-network targets, or explicitly document arbitrary outbound webhooks with SSRF controls.

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

Labels

cla-signed external-contributor Indicates that a PR has been opened by someone outside the Warp team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant