This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git


The following commit(s) were added to refs/heads/main by this push:
     new 10624e2  feat(gmail-threading): prefer the primary reporter thread 
over a forwarder/relay thread (#131)
10624e2 is described below

commit 10624e254534b2a9ef79370b3400df3af743e3ab
Author: Jarek Potiuk <[email protected]>
AuthorDate: Tue May 12 01:37:27 2026 +0200

    feat(gmail-threading): prefer the primary reporter thread over a 
forwarder/relay thread (#131)
    
    When a tracker's *Security mailing list thread* body field records
    two inbound Gmail threads — typically the original direct report
    *and* a separate forwarder/relay thread that landed afterwards
    (huntr.com bounty relay, GHSA forward, HackerOne forward,
    ASF-security relay) — default reply drafts must target the
    **primary reporter's** thread, not the forwarder's relay thread.
    The relay thread is kept on the tracker for record-keeping and is
    only used for the rarer back-channel relay message (e.g. "please
    ask the external reporter to confirm a credit form").
    
    Codifies the rule and detection signals (`via huntr.com`,
    `ASF-relayed`, `relay`, `forwarded by`, `<provider>-class
    duplicate`, etc.) in `tools/gmail/threading.md`, scopes the
    existing `asf-relay.md` shape to the relay-thread-only case, and
    adds one-paragraph pointers in the three drafting skills that
    reply on an existing tracker's threads — `security-issue-sync`
    (status updates), `security-cve-allocate` (CVE-allocated
    notification), `security-issue-invalidate` (close-as-invalid
    reply). No reshape of the existing line-per-reporter body-field
    convention is needed.
    
    Surfaced by airflow-s/airflow-s#351, where a primary report by
    Vincent55 was followed by an Aymane Maguiti huntr.com bounty
    relay; without this rule a future reply draft could land on the
    relay thread, ASF-forwarder visible, instead of going back to the
    primary reporter directly.
    
    Generated-by: Claude Code (Opus 4.7)
---
 .claude/skills/security-cve-allocate/SKILL.md     | 10 ++-
 .claude/skills/security-issue-invalidate/SKILL.md |  1 +
 .claude/skills/security-issue-sync/SKILL.md       | 10 +++
 tools/gmail/asf-relay.md                          | 17 +++++
 tools/gmail/threading.md                          | 86 +++++++++++++++++++++++
 5 files changed, 122 insertions(+), 2 deletions(-)

diff --git a/.claude/skills/security-cve-allocate/SKILL.md 
b/.claude/skills/security-cve-allocate/SKILL.md
index 6ca2171..5bc6f95 100644
--- a/.claude/skills/security-cve-allocate/SKILL.md
+++ b/.claude/skills/security-cve-allocate/SKILL.md
@@ -477,9 +477,15 @@ user to confirm. Numbered items:
    `threadId` from the tracker's *security-thread* body field
    (for Airflow, *"Security mailing list thread"*); subject is always
    `Re: <root subject of the inbound report>`, never fabricated.
+   When the field records multiple threads — a primary reporter thread
+   *and* one or more forwarder/relay threads — the CVE-allocated
+   status update goes to the **primary** thread per
+   [`tools/gmail/threading.md` — Selecting the inbound thread when multiple 
are 
recorded](../../../tools/gmail/threading.md#selecting-the-inbound-thread-when-multiple-are-recorded).
    Surface which backend was used and which threading path the draft
-   took (thread-attached vs subject fallback) in the Step 5 proposal.
-   See [`tools/gmail/threading.md`](../../../tools/gmail/threading.md)
+   took (thread-attached vs subject fallback), and — when multiple
+   threads were on the tracker — which one was selected as primary,
+   in the Step 5 proposal. See
+   [`tools/gmail/threading.md`](../../../tools/gmail/threading.md)
    for the full threading rule including the few cases where the
    skill should stop rather than fall back.
 
diff --git a/.claude/skills/security-issue-invalidate/SKILL.md 
b/.claude/skills/security-issue-invalidate/SKILL.md
index e2f34c8..89aa310 100644
--- a/.claude/skills/security-issue-invalidate/SKILL.md
+++ b/.claude/skills/security-issue-invalidate/SKILL.md
@@ -245,6 +245,7 @@ the close. Read the *Security mailing list thread* body 
field:
 |---|---|---|
 | Real `lists.apache.org` URL or any URL | `security@`-imported 
(public-archive case) | Draft on the original Gmail thread; locate via the 
rollup-comment `threadId` reference. |
 | `No public archive URL — tracked privately on Gmail thread <threadId>` 
(sentinel from [`security-issue-import`](../security-issue-import/SKILL.md) 
Step 7) | `security@`-imported (Gmail-only case) | Draft on the named 
`<threadId>`. |
+| **Multiple lines** — primary reporter thread plus one or more 
forwarder/relay threads (huntr.com, GHSA, HackerOne, ASF-security relay) | 
`security@`-imported, with a relay second thread | Draft on the **primary 
reporter thread** per [`tools/gmail/threading.md` — Selecting the inbound 
thread when multiple are 
recorded](../../../tools/gmail/threading.md#selecting-the-inbound-thread-when-multiple-are-recorded).
 The relay thread is for back-channel relay only; the invalid-close reply goes  
[...]
 | `N/A — opened from public PR <upstream>#<N>; no security@ thread` (sentinel 
from 
[`security-issue-import-from-pr`](../security-issue-import-from-pr/SKILL.md)) | 
PR-imported | **Skip** the email-draft step. No reporter exists to notify. |
 | Empty / `_No response_` / unrecognised | Indeterminate | Surface to the 
user; ask whether the tracker has a Gmail thread the skill should reply on, or 
whether the close is silent (no email). |
 
diff --git a/.claude/skills/security-issue-sync/SKILL.md 
b/.claude/skills/security-issue-sync/SKILL.md
index d1ef432..54d7f69 100644
--- a/.claude/skills/security-issue-sync/SKILL.md
+++ b/.claude/skills/security-issue-sync/SKILL.md
@@ -567,6 +567,16 @@ Process for finding the real reporter and the original 
thread:
      when drafting status updates,
    - the original subject line (you will reuse it for In-Reply-To threading).
 
+   **When the tracker records multiple inbound threads** — a primary
+   reporter thread *and* one or more forwarder/relay threads (huntr.com,
+   GHSA, HackerOne, ASF-security relay) — select the primary reporter's
+   thread per
+   [`tools/gmail/threading.md` — Selecting the inbound thread when multiple 
are 
recorded](../../../tools/gmail/threading.md#selecting-the-inbound-thread-when-multiple-are-recorded).
+   Default status-update drafts target the primary thread; the relay
+   thread is reserved for back-channel relay questions only. Surface
+   the primary/secondary selection in the Step 2b proposal so the user
+   sees which thread the draft will attach to.
+
 4. **Read the full thread** with
    `mcp__claude_ai_Gmail__gmail_read_thread <threadId>` and extract:
 
diff --git a/tools/gmail/asf-relay.md b/tools/gmail/asf-relay.md
index d0bee07..81fee2b 100644
--- a/tools/gmail/asf-relay.md
+++ b/tools/gmail/asf-relay.md
@@ -28,6 +28,23 @@ of confirmation, credit-preference request, status update — 
the
 threading rules from [`threading.md`](threading.md) all still apply;
 the differences are in the headers and body shape.
 
+**Scope of this file: relay thread is the *only* thread.** The
+relay-specific shape below (different `To:`, brevity, link to the
+external reference) applies when the inbound relay thread is the
+**only** thread recorded on the tracker — i.e. the external
+reporter never sent a direct message to the project's security
+list, so the relay is the only path back to them. When the tracker
+records **two threads** — a direct primary reporter thread *and* a
+separate forwarder/relay thread (typical when an external reporter
+filed directly *and* the same bug was later relayed by huntr.com /
+GHSA / ASF-security) — default drafts go to the **primary** thread
+per [`threading.md` — Selecting the inbound thread when multiple
+are 
recorded](threading.md#selecting-the-inbound-thread-when-multiple-are-recorded),
+and the relay-specific shape below applies only to the rarer
+back-channel message the project sends *through* the relay
+channel (e.g. *"please ask the external reporter to confirm a
+credit form for the advisory"*).
+
 Placeholder convention:
 
 - `<security-list>` — the project's security list. The concrete
diff --git a/tools/gmail/threading.md b/tools/gmail/threading.md
index 104e948..6763d89 100644
--- a/tools/gmail/threading.md
+++ b/tools/gmail/threading.md
@@ -4,6 +4,7 @@
 
 - [Gmail — drafts stay on the inbound 
thread](#gmail--drafts-stay-on-the-inbound-thread)
   - [The rule](#the-rule)
+  - [Selecting the inbound thread when multiple are 
recorded](#selecting-the-inbound-thread-when-multiple-are-recorded)
   - [Fallback — subject-matched draft when `replyToMessageId` is 
unavailable](#fallback--subject-matched-draft-when-replytomessageid-is-unavailable)
   - [Special case — ASF-security relay](#special-case--asf-security-relay)
 
@@ -73,6 +74,91 @@ like live here.
   overlap; it requires `threadId` or (as the fallback) a matching
   subject plus the right `In-Reply-To` / `References` headers.
 
+## Selecting the inbound thread when multiple are recorded
+
+A tracker's *Security mailing list thread* body field can hold
+more than one thread when:
+
+- a second reporter independently filed the same root-cause bug
+  through a different channel and
+  
[`security-issue-deduplicate`](../../.claude/skills/security-issue-deduplicate/SKILL.md)
+  merged the two trackers (one line per reporter, per the dedupe
+  skill's body-field shape); or
+- an external reporter's report reached the project's security
+  list via a separate forwarder thread — e.g. a huntr.com bounty
+  relayed by `@apache/security`, a GHSA forward, a HackerOne
+  forward — *after* the original direct report had already been
+  imported, so the secondary thread was appended to record the
+  duplicate-of provenance.
+
+**The rule: default drafts go to the primary reporter's thread,
+never to a forwarder/relay thread.** The forwarder/relay thread
+is kept on the tracker for record-keeping and for back-channel
+relay questions only (e.g. *"please ask the external reporter to
+confirm a credit form"*) — see [`asf-relay.md`](asf-relay.md) for
+the relay-shape body language.
+
+The primary reporter is the one whose name appears in
+*Reporter credited as* without a relay annotation, whose direct
+email started the security-list thread chronologically first, and
+whose line in *Security mailing list thread* does **not** carry
+any of the forwarder signals below.
+
+**Forwarder/relay signals — match case-insensitively in the line's
+annotation text** (everything around the `threadId` reference):
+
+- `via huntr.com`, `via GHSA`, `via HackerOne`, `via bugcrowd`,
+  `via <any bounty platform>`
+- `ASF-relayed`, `ASF-security relay`, `ASF-security-relay`,
+  `relayed by @apache/security`, `relayed by`
+- `forwarder`, `forwarded by`, `relay`, `relayed`
+- `huntr.com bounty <id>-class duplicate`,
+  `<provider>-class duplicate`
+
+If a line has any of these signals it is **secondary**; the line
+without any of these signals (or — for legacy trackers that
+predate the convention — the chronologically-first thread
+mentioned) is **primary**.
+
+Worked example. The body field on a real tracker reads:
+
+```
+No public archive URL — tracked privately on Gmail thread `19dc8d4675dfc1f1`.
+Aymane Maguiti (huntr.com bounty `abdbcf11-…`-class duplicate, ASF-relayed by 
@apache/security on 2026-05-04T09:22:25Z): Gmail thread `19def0954b27ac31`.
+```
+
+- Line 1 → primary (no relay signal). Use `19dc8d4675dfc1f1` for
+  every default reply: receipt-of-confirmation, credit-question,
+  CVE-allocated status update, advisory-shipped follow-up.
+- Line 2 → secondary (matches `via huntr.com`-class, `ASF-relayed`).
+  Use `19def0954b27ac31` only when the project needs to relay a
+  question back through huntr.com to the external reporter and the
+  primary thread cannot deliver it.
+
+**Edge cases:**
+
+- **Only one thread recorded, with relay signals.** Classic
+  ASF-security-relay case. Follow [`asf-relay.md`](asf-relay.md);
+  there is no primary thread to fall back to.
+- **Only one thread recorded, no relay signals.** Standard
+  single-reporter case; the thread is the primary by default.
+- **Both lines carry relay signals.** Rare — typically a
+  third-party reporter relayed by two different channels.
+  Surface to the user before drafting; do not pick a "least
+  forwarded" line silently.
+- **Neither line carries a `threadId`** (PonyMail URL only, no
+  Gmail identifier). The tracker pre-dates the Gmail-threadId
+  convention; fall back to the rollup-comment `threadId` lookup
+  per the per-skill recipes.
+
+Surface the primary/secondary selection in the skill's proposal
+so the user sees which thread the draft attaches to (*"Drafting
+on primary reporter thread `19dc8d4675dfc1f1` (Vincent55); the
+secondary huntr.com-relay thread `19def0954b27ac31` was excluded
+from default reply targets."*). The user can override per draft
+if a specific message genuinely needs to go through the relay
+channel instead.
+
 ## Fallback — subject-matched draft when `replyToMessageId` is unavailable
 
 Thread attachment is the first-choice path, but the skills must also

Reply via email to