From ab662691555a9eb973191c1e8b620b29fcbbc573 Mon Sep 17 00:00:00 2001 From: Hermes Date: Tue, 25 Aug 2026 07:39:21 -0400 Subject: [PATCH] fix(antigravity): strip any competitor system prompt, not one literal Google answers a systemInstruction advertising a rival agent with 429 RESOURCE_EXHAUSTED - a content refusal shaped like a quota error, which is why it survived the endpoint and backoff fixes. Upstream PR #3223 stripped a single Zed literal, so Claude Code / Hermes wording still tripped it. Match the sentence shape instead. Check fails on the old one-literal code and passes here. --- .../antigravity-competitor-strip.check.mjs | 57 +++++++++++++++++++ open-sse/executors/antigravity.js | 24 +++++--- 2 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 open-sse/executors/antigravity-competitor-strip.check.mjs diff --git a/open-sse/executors/antigravity-competitor-strip.check.mjs b/open-sse/executors/antigravity-competitor-strip.check.mjs new file mode 100644 index 00000000..b3b1eb5f --- /dev/null +++ b/open-sse/executors/antigravity-competitor-strip.check.mjs @@ -0,0 +1,57 @@ +/** + * #3223-generalised: Antigravity answers a system prompt that advertises a rival + * agent with 429 RESOURCE_EXHAUSTED. Upstream stripped one Zed literal; every + * other client wording still tripped it. This pins the shapes we must strip. + * + * Run: node open-sse/executors/antigravity-competitor-strip.check.mjs + */ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import vm from 'node:vm'; + +const here = dirname(fileURLToPath(import.meta.url)); +const src = readFileSync( + process.env.AG_SOURCE || resolve(here, 'antigravity.js'), + 'utf8' +); + +// Lift the strip block out of buildRequest and run it against a fake request. +const start = src.indexOf('if (requestWithoutTools.systemInstruction?.parts)'); +assert.ok(start >= 0, 'competitor-strip block must exist'); +const end = src.indexOf('\n }\n', src.indexOf('for (const part', start)) + 6; +const block = src.slice(start, end); + +const strip = (text) => { + const requestWithoutTools = { systemInstruction: { parts: [{ text }] } }; + vm.runInNewContext(block, { requestWithoutTools }); + return requestWithoutTools.systemInstruction.parts[0].text; +}; + +// The literal upstream already handled — must keep working. +assert.ok( + !strip("You are a Claude agent, built on Anthropic's Claude Agent SDK. Be terse.") + .includes('Claude'), + 'Zed wording must still be stripped' +); + +// The wordings upstream missed. +for (const prompt of [ + "You are Claude Code, Anthropic's official CLI for Claude. Help the user.", + 'You are Claude, made by Anthropic!', + 'you are a claude agent. lowercase should not save it.', +]) { + const out = strip(prompt); + assert.ok(!/claude/i.test(out), `must strip competitor branding from: ${prompt}`); +} + +// Must not eat an innocent prompt. +const benign = 'You are a helpful assistant. Answer in French.'; +assert.equal(strip(benign), benign, 'benign prompts must pass through untouched'); + +// Non-string parts must not throw. +const req = { systemInstruction: { parts: [{ inlineData: {} }] } }; +vm.runInNewContext(block, { requestWithoutTools: req }); + +console.log('antigravity-competitor-strip: all passed'); diff --git a/open-sse/executors/antigravity.js b/open-sse/executors/antigravity.js index 07bbb4fc..5c9b7322 100644 --- a/open-sse/executors/antigravity.js +++ b/open-sse/executors/antigravity.js @@ -246,17 +246,27 @@ export class AntigravityExecutor extends BaseExecutor { const { tools: _originalTools, toolConfig: _originalToolConfig, ...requestWithoutTools } = body.request || {}; stripBlacklisted(requestWithoutTools); - // Rewrite competitive system prompts (e.g. Zed IDE's Claude prompt) to prevent Antigravity from - // flagging the request and immediately blocking it with a 429 Quota Exhausted response. + // Rewrite competitive system prompts before they reach Antigravity. Google + // inspects systemInstruction and answers a prompt that advertises a rival + // agent with 429 RESOURCE_EXHAUSTED — a content refusal wearing a quota + // error's clothes, which is why it survives every backoff and endpoint fix. + // Upstream PR #3223 stripped ONE literal (Zed's "You are a Claude agent, + // built on Anthropic's Claude Agent SDK."), so every other client — Claude + // Code, Hermes, Cline — still trips it. Match the shape instead. + // ponytail: regex over a prompt-classifier; widen the alternation if a new + // client wording slips through. if (requestWithoutTools.systemInstruction?.parts) { - const oldText = "You are a Claude agent, built on Anthropic's Claude Agent SDK."; for (const part of requestWithoutTools.systemInstruction.parts) { - if (typeof part.text === "string" && part.text.includes(oldText)) { - part.text = part.text.split(oldText).join(""); - } + if (typeof part.text !== "string") continue; + part.text = part.text + // "You are Claude Code, Anthropic's official CLI for Claude." / + // "You are a Claude agent, built on Anthropic's Claude Agent SDK." + .replace(/You are (?:a |an )?Claude\b[^.!?]*[.!?]\s*/gi, "") + // Residual vendor branding in the same sentence position. + .replace(/\b(?:Anthropic's|Anthropic)\s+(?:official\s+)?(?:CLI|Claude Agent SDK)\b[^.!?]*[.!?]\s*/gi, "") + .trimStart(); } } - const generationConfig = { ...(requestWithoutTools.generationConfig || {}) }; if (generationConfig.maxOutputTokens > MAX_ANTIGRAVITY_OUTPUT_TOKENS) { generationConfig.maxOutputTokens = MAX_ANTIGRAVITY_OUTPUT_TOKENS;