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