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 c4ff7fd docs(gmail,security): verify draft persistence before
claiming "still pending" (#355)
c4ff7fd is described below
commit c4ff7fd65318c887ad94a8eff797146a27669cd4
Author: Jarek Potiuk <[email protected]>
AuthorDate: Thu May 28 05:29:57 2026 +0200
docs(gmail,security): verify draft persistence before claiming "still
pending" (#355)
Add a verify-before-claim rule to the gmail tool's `operations.md`:
any skill that writes a status comment, proposal, or recap line of
the shape *"Reporter notification still pending — see draft
`<draftId>`"* MUST call `mcp__claude_ai_Gmail__list_drafts` first
and confirm the `draftId` is still in the Drafts folder. If it is
gone (sent or discarded), flip the line — never assert "still
pending" without checking.
Reference the rule from the two emission sites that previously
wrote the "still pending" line unconditionally:
- `security-cve-allocate/SKILL.md` § Reporter-notification line
options
- `security-issue-sync/SKILL.md` § Reporter-notification line
options (Step 4 status-rollup) — also threads the rule into the
existing stale-draft-flag carry-forward guard
== Why ==
Without the guard, a "still pending" flag posted on sync N
self-replicates across every subsequent sync long after the user
has actually sent the email — the same self-replication failure
the existing stale-draft-flag rule guards against, but on the
positive side ("still pending" instead of "discard manually"). The
fix is mechanical: one `list_drafts` call before emitting the
line, costing one MCP round-trip per pass and closing a whole
class of false-claim status comments.
Generated-by: Claude Code (Opus 4.7)
---
.claude/skills/security-cve-allocate/SKILL.md | 9 +++++++
.claude/skills/security-issue-sync/SKILL.md | 17 ++++++++++++-
tools/gmail/operations.md | 35 +++++++++++++++++++++++++++
3 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/.claude/skills/security-cve-allocate/SKILL.md
b/.claude/skills/security-cve-allocate/SKILL.md
index 2c3fe3a..34dfa0d 100644
--- a/.claude/skills/security-cve-allocate/SKILL.md
+++ b/.claude/skills/security-cve-allocate/SKILL.md
@@ -559,6 +559,15 @@ End the visible part with exactly one of:
team)."* — for team-discovered issues.
- *"Reporter notification still pending — see draft `<draftId>`."* —
when a draft was created but the user has not yet sent it.
+ **Before emitting this line**, verify via
+ `mcp__claude_ai_Gmail__list_drafts` that `<draftId>` is still in
+ the user's Drafts folder. If it is not (the user sent it between
+ draft creation and this status-comment post, or discarded it),
+ flip to *"Reporter draft `<draftId>` is no longer in Drafts —
+ sent or discarded."* — never assert "still pending" without
+ checking. See the
+ [verify-before-claim
rule](../../../tools/gmail/operations.md#verify-before-claim--never-assert-a-draft-is-still-pending-without-checking)
+ for the full rationale.
- Omit the line entirely when no reporter notification is
meaningful (e.g. an automated scanner report the team has decided
to treat as non-actionable).
diff --git a/.claude/skills/security-issue-sync/SKILL.md
b/.claude/skills/security-issue-sync/SKILL.md
index b345045..a22d30d 100644
--- a/.claude/skills/security-issue-sync/SKILL.md
+++ b/.claude/skills/security-issue-sync/SKILL.md
@@ -765,7 +765,11 @@ status comment — the flag has self-replicated once and
will keep going
forever if every sync copies it forward blindly. If the verification
step itself fails (Gmail 500, API timeout), say so explicitly rather
than defaulting to "assume stale"; silent replication is the failure
-mode to avoid.
+mode to avoid. This is one application of the broader
+[verify-before-claim
rule](../../../tools/gmail/operations.md#verify-before-claim--never-assert-a-draft-is-still-pending-without-checking)
—
+the same `list_drafts` guard also applies before the
+"Reporter notification still pending — see draft `<draftId>`" line in
+the Step 4 status-rollup entry below.
Do **not** act on signals automatically; as always, each one becomes a
numbered proposal item in Step 2 and only applies after user
@@ -1568,6 +1572,17 @@ will change and *why*. Group them by category:
the security team and is already in the loop.
- *"Reporter notification still pending — see draft `<draftId>`."*
— if a draft was created but the user has not yet sent it.
+ **Before emitting this line**, call
+ `mcp__claude_ai_Gmail__list_drafts` and confirm `<draftId>` is
+ in the result. If it is gone (sent or discarded between draft
+ creation and this status-comment post), flip to *"Reporter
+ draft `<draftId>` is no longer in Drafts — sent or
+ discarded."* — never assert "still pending" without checking.
+ This rule applies on **every** sync that emits the line,
+ including the sync that created the draft (the user may have
+ switched to Gmail and sent it before the comment landed). See
+ the [verify-before-claim
rule](../../../tools/gmail/operations.md#verify-before-claim--never-assert-a-draft-is-still-pending-without-checking)
+ for the full rationale.
**Summary action-label for a sync pass** — see the table in
[`status-rollup.md`](../../../tools/github/status-rollup.md#summary--action-labels).
diff --git a/tools/gmail/operations.md b/tools/gmail/operations.md
index 5fbdf29..5f188cf 100644
--- a/tools/gmail/operations.md
+++ b/tools/gmail/operations.md
@@ -13,6 +13,7 @@
- [Create draft — `oauth_curl` backend](#create-draft--oauth_curl-backend)
- [Hard rules that apply to both
backends](#hard-rules-that-apply-to-both-backends)
- [List drafts](#list-drafts)
+ - [Verify-before-claim — never assert a draft is "still pending" without
checking](#verify-before-claim--never-assert-a-draft-is-still-pending-without-checking)
- [Hard limitation — no update, no
delete](#hard-limitation--no-update-no-delete)
- [Confidentiality of drafts](#confidentiality-of-drafts)
- [Error handling](#error-handling)
@@ -240,6 +241,40 @@ in a previous status comment still exists before carrying
the flag
forward. See the *"self-replicating stale-draft flag"* paragraph in
that skill.
+### Verify-before-claim — never assert a draft is "still pending" without
checking
+
+Any skill that writes a tracker status comment, proposal, or recap
+line of the shape *"Reporter notification still pending — see draft
+`<draftId>`"* (or any analogous "draft is awaiting send" claim) MUST
+call `list_drafts` immediately before emitting the line and confirm
+`<draftId>` is in the returned set.
+
+- **If the `draftId` is in the result** → emit the "still pending"
+ line as planned.
+- **If the `draftId` is NOT in the result** → the draft is gone:
+ the user has either sent it (Gmail moves sent items out of Drafts)
+ or discarded it. Do **not** emit "still pending". Instead, flip
+ the line to one of:
+ - *"Reporter draft `<draftId>` is no longer in Drafts — sent or
+ discarded (verify in Sent if uncertain)."* — neutral, no false
+ claim.
+ - *"Reporter has been notified on the original mail thread."* —
+ only if the skill can independently confirm the send (e.g. via
+ `list_sent_since` filtered to the recipient, or
+ `get_thread(threadId)` showing a SENT message after the draft
+ was created).
+
+The rule applies in **every** sync, not only on stale-flag
+carry-forward. The "draft was just created in this same pass" case
+is no exception — the user may have switched to Gmail and sent it
+between the create call and the status-comment post; one extra
+`list_drafts` call covers the race.
+
+Without this guard, a "still pending" flag posted on one sync
+self-replicates across every subsequent sync long after the user
+has actually sent the email, nagging the team about a phantom
+pending notification.
+
## Hard limitation — no update, no delete
The Gmail MCP exposes **`create`, `list`, and `read` only** for