← back to George Mcp
george-mcp: retry once on a Gmail quota error before fail-closed refusal — TK-11231
4cdbff59858593542397997ff4f5d1a41ee20ec6 · 2026-09-10 08:03:41 -0700 · Steve Abrams
Red-team finding: dedupe adds a search to every create, and fail-closed turns a
quota blip into a hard block on legitimate drafting. The 60s id-join cache does
not help a burst of drafts to DIFFERENT recipients — which is exactly the
vendor-blast pattern (Command, Maya Romanoff, Newmor back to back) that produced
the original backlog. Retry once with backoff on a quota/429 error, then refuse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTxE4MgnygQ9EY2rPZvtcK
Files touched
Diff
commit 4cdbff59858593542397997ff4f5d1a41ee20ec6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 10 08:03:41 2026 -0700
george-mcp: retry once on a Gmail quota error before fail-closed refusal — TK-11231
Red-team finding: dedupe adds a search to every create, and fail-closed turns a
quota blip into a hard block on legitimate drafting. The 60s id-join cache does
not help a burst of drafts to DIFFERENT recipients — which is exactly the
vendor-blast pattern (Command, Maya Romanoff, Newmor back to back) that produced
the original backlog. Retry once with backoff on a quota/429 error, then refuse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTxE4MgnygQ9EY2rPZvtcK
---
index.js | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/index.js b/index.js
index e9da4da..c2fa07d 100755
--- a/index.js
+++ b/index.js
@@ -331,6 +331,20 @@ const TOOLS = [
},
];
+// Dedupe adds a Gmail search to every create, and Gmail's per-minute "Total Query Cost"
+// quota is bursty — a blast of drafts to different recipients can trip it. Since the check
+// now fails CLOSED, an un-retried blip would hard-block legitimate drafting. Retry once.
+async function searchDraftsTo(to, account) {
+ const q = `in:draft to:${to}`;
+ try {
+ return await george("/api/search", { query: { q, maxResults: 10, account } });
+ } catch (e) {
+ if (!/quota|rate limit|429|userRateLimit/i.test(e.message)) throw e;
+ await new Promise((r) => setTimeout(r, 2500));
+ return await george("/api/search", { query: { q, maxResults: 10, account } });
+ }
+}
+
// ─── Draft return-contract helpers (TK-11231) ───────────────────────────────
// A draft is NOT a delivered outcome. The deepest cause of the unsent-draft
// backlog was that creating a draft returned a plain success, so assistants
@@ -526,10 +540,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
if (!args.acknowledge_existing && args.to) {
let existing;
try {
- const dupes = await george("/api/search", {
- query: { q: `in:draft to:${args.to}`, maxResults: 10, account },
- });
- existing = dupes.messages || [];
+ existing = (await searchDraftsTo(args.to, account)).messages || [];
} catch (e) {
// FAIL CLOSED. Swallowing this is what silently re-opens the duplicate hole:
// during testing a Gmail per-minute quota error made the check a no-op and a
← accb909 george-mcp: drafts are no longer a terminal success — TK-112
·
back to George Mcp
·
(newest)