Table of Contents generated with DocToc
Every drafted email that relates to a tracking issue should attach to the original inbound Gmail thread — the thread whose threadId was recorded when the tracker was imported. Gmail‘s server-side threader attaches by message-ID-of-parent (passed as replyToMessageId on the MCP, or by threadId on the OAuth path); other mail clients (Thunderbird, Outlook, Apple Mail, the recipient’s own client) thread by the MIME In-Reply-To / References headers and the subject line, which Gmail synthesises from the parent message in either case.
Both supported drafting backends now provide thread-attachment — see draft-backends.md:
| Backend | Thread attach | Mechanism |
|---|---|---|
oauth_curl (preferred) | yes | threadId plus explicit In-Reply-To / References headers |
claude_ai_mcp (discouraged — see draft-backends.md) | yes | replyToMessageId — the message ID of the chronologically-last message on the inbound thread |
The two threading paths available to the skills, in preferred order:
get_thread) and pass it to replyToMessageId, or pass the threadId to oauth-draft-create --thread-id.threadId) or the inbound subject is unsafe to match on. Both backends fall back to this by simply omitting the thread-attachment parameter.The call shape (signature, kwargs, no-send rule) per backend lives in operations.md — Drafting backends; the rules on which thread to use and what the other fields look like live here.
threadId when known. Whatever the recipient change — a reporter reply, an ASF-security relay request, a PMC credit question, a follow-up asking for a PoC — the draft should attach to the inbound tracker's threadId. A triager reading the Gmail conversation view should see every exchange on a single tracker in one place; if threading breaks, that history scatters across two conversations.Re: <root subject>, never a fabricated new one. Gmail‘s own threading survives without matching subjects when threadId is set, but other clients commonly fall back to subject-based threading. A drifted subject looks like a broken conversation on half the world’s mail readers. The root subject is the subject of the first message on the inbound thread — not the last reply's subject line, which Gmail may have displayed with a prefix trim.To: may differ from the original correspondents. It is fine to address a draft to a specific person (an ASF security-team member who relayed the report, a named PMC member, an individual reporter) even if the original inbound root was addressed to a list. Threading does not require recipient overlap; it requires threadId or (as the fallback) a matching subject plus the right In-Reply-To / References headers.A tracker's Security mailing list thread body field can hold more than one thread when:
security-issue-deduplicate merged the two trackers (one line per reporter, per the dedupe skill's body-field shape); or@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 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 byforwarder, forwarded by, relay, relayedhuntr.com bounty <id>-class duplicate, <provider>-class duplicateIf 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`.
19dc8d4675dfc1f1 for every default reply: receipt-of-confirmation, credit-question, CVE-allocated status update, advisory-shipped follow-up.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:
asf-relay.md; there is no primary thread to fall back to.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.
replyToMessageId is unavailableThread attachment is the first-choice path, but the skills must also work in cases where the inbound thread cannot be resolved:
../github/issue-template.md for the field role).threadId in that field is stale (the thread was deleted or archived out of the user's Gmail search index).get_thread returns no messages (the thread exists but has been emptied), so there is no replyToMessageId to point at.draft-backends.md applies — drop thread attachment for the new draft so it surfaces in the global Drafts folder.In these cases, create the draft with replyToMessageId omitted (or --thread-id omitted on the OAuth path) but with the matching subject line from the inbound message. Gmail will start a new conversation on the sender‘s side, but most other clients (and Gmail’s own subject-fallback behaviour on the recipient's side) will still thread the reply by subject. This is not as good as a thread-attached draft, but it is substantially better than either fabricating a new subject or not drafting at all.
Surface the degraded threading in the skill's proposal so the user knows which path the draft took:
replyToMessageId to message <msg-id-prefix>... on thread <thread-id-prefix>....” — the good case (default claude_ai_mcp backend).threadId (via oauth_curl backend).” — the good case for the OAuth-opt-in user.<reason>). Gmail shows it as a new conversation server-side; the recipient's client will thread it via the Re: <subject> match.”When the fallback kicks in, record the reason on the tracker's status comment so the next sync run can see why a new Gmail thread appeared. Do not silently drop to fallback — it changes the shape of the conversation the reporter sees.
When fallback is not appropriate. Some cases genuinely warrant stopping rather than drafting on a mismatched subject. Examples:
When the inbound report arrives via an ASF forwarder rather than directly from the external reporter, the drafting shape changes slightly (different To: / Cc:, relay-specific body language) but the threading rules above are unchanged: resolve and attach to the inbound thread (replyToMessageId on the default backend, threadId on oauth_curl); fall back to the inbound subject when the thread cannot be resolved. See asf-relay.md.