[GH-ISSUE #2832] [Bug] Gemini-backed clients reject the whole tool catalog: advertised schemas emit dependentRequired / dependencies / $schema #1011

Closed
opened 2026-10-15 18:30:19 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @jzPPP3333 on GitHub (Oct 13, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2832

### Summary

rea's advertised tool input schemas carry JSON Schema keywords that Google's functionDeclarations[].parameters does not accept (dependentRequired, dependencies, $schema). A single offending tool makes the entire request fail with HTTP 400, so every tool in the catalog becomes unavailable for the whole session on any Gemini-backed client.

Invalid JSON payload received. Unknown name "dependentRequired" at
'request.tools[0].function_declarations[131].parameters': Cannot find field.
Status: 400

Environment

rea-agents 6.4.0 (npm latest is also 6.4.0, so upgrading does not help)
Node v24.15.0
Client Claude Code 2.1.295 (claude-desktop-3p), POST /v1/messages?beta=true
Transport local Anthropic鈫扜emini gateway, upstream https://daily-cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse
MCP registration {"command":"node","args":[".../rea-agents/scripts/rea.mjs","mcp"]}
Tools in the failing request 283

Observed vs expected

  • Observed: 400 from the upstream on every turn. The gateway forwards tools[].input_schema into tools[0].function_declarations[].parameters; the offending declaration is mcp__rea__compare_web_captures at index 131.
  • Expected: a tool catalog that survives translation into the vendors' function-calling dialects, or at least degrades to per-tool unavailability instead of failing the whole request.

I isolated the offender by walking the captured request body recursively: of 283 tools exactly one contained dependentRequired, and its index was exactly the 131 Google named.

Notably the same request also carried $schema on all 283 tools plus propertyNames (index 1) and oneOf (index 112). The gateway already strips those, so only dependentRequired leaked through 鈥?consistent with a strip-list that predates JSON Schema 2019-09.

Root cause

Two presentation-layer emitters advertise keywords that are not part of Google's protobuf Schema message:

1. dist/contracts/inputSchemaPresentation.js 鈫?flattenRootObjectUnion

When a root-level anyOf/oneOf of strict object groups is flattened into a single object, the "a field unique to a group still needs that group's required partners" hint is emitted as a schema-map keyword, choosing the keyword by $schema dialect:

...(Object.keys(dependencies).length > 0
    ? {
        [root.$schema === "http://json-schema.org/draft-07/schema#"
            ? "dependencies"
            : "dependentRequired"]: dependencies,
    }
    : {}),

Both dependencies and dependentRequired are unknown to Gemini's Schema. The same function also spreads ...rest, which re-advertises the root $schema.

2. dist/contracts/compactInputSchema.js 鈫?reduceToShallowProperties

const $schema = typeof schema.$schema === "string" ? { $schema: schema.$schema } : {};
...
...$schema,
...
...(isSchemaObject(schema.dependentRequired)
    ? { dependentRequired: { ...schema.dependentRequired } }
    : {}),

$schema is re-advertised and dependentRequired is deliberately preserved through the reduction. (This path is only reachable when the inline form exceeds the byte budget 鈥?easy to miss, since the inline path preserves whatever the caller passed.)

Minimal reproduction

import { presentInputJsonSchema } from "rea-agents/dist/contracts/inputSchemaPresentation.js";

presentInputJsonSchema({
  $schema: "https://json-schema.org/draft/2020-12/schema",
  anyOf: [
    { type: "object", properties: { a: { type: "string" }, b: { type: "string" } }, required: ["a"] },
    { type: "object", properties: { c: { type: "string" }, d: { type: "string" } }, required: ["c"] },
  ],
}, (s) => ({ ...s }));

Returns (2020-12 鈫?dependentRequired; draft-07 鈫?dependencies), plus a root $schema:

{
  "type": "object",
  "description": "Provide a complete input group: a + [b]; c + [d].",
  "properties": { "a": { "type": "string" }, "b": { "type": "string" }, "c": { "type": "string" }, "d": { "type": "string" } },
  "minProperties": 1,
  "dependentRequired": { "b": ["a"], "d": ["c"] },
  "additionalProperties": false
}

Impact

Any Gemini-backed client (Gemini CLI, Antigravity/Cloud Code gateways, or any Anthropic鈫扜emini bridge) loses the whole tool catalog 鈥?not just the one tool 鈥?for as long as the catalog includes a schema with a mutually-exclusive input group. compare_web_captures is one such tool today; any future tool whose root is a union of object groups will reintroduce it.

Suggested fix

The advertised form is presentation-only 鈥?dist/contracts/inputSchemaPresentation.js itself notes "The canonical Zod schema still enforces the exact group." So the hint can move into description and the keywords can be dropped:

b requires a; d requires c.

Options, in order of preference:

  1. Never advertise dependencies / dependentRequired / $schema. Carry the partner-field guidance in description (it is already built right above the emission site).
  2. Gate them on a per-client capability flag (Anthropic/OpenAI accept them, Google does not) so the union-flattening hint survives where it is legal.

Either way, please also assert this in the existing Gemini client verification (verify:gemini-client) by validating the advertised catalog against Google's actual Schema field set 鈥?see related #1932 / #1935. A cheap guard is to reject any advertised schema keyword that is not in Google's Schema message whenever a Gemini client is detected.

Workaround applied locally

Stripped the four emission sites (and re-emitted the partner hint as description text). Verified against the compiled modules:

  • draft-07 root union, 2020-12 root union, and the forced reduceToShallowProperties path all no longer emit $schema / dependencies / dependentRequired;
  • the hint survives: "description": "Provide a complete input group: a + [b]; c + [d]. b requires a; d requires c."
Local patch (dist-level, for reference only)
diff --git a/dist/contracts/inputSchemaPresentation.js b/dist/contracts/inputSchemaPresentation.js
index eb7351f..e3a9236 100644
--- a/dist/contracts/inputSchemaPresentation.js
+++ b/dist/contracts/inputSchemaPresentation.js
@@ -117,7 +117,8 @@ const inputGroup = (branch) => {
 // root union of strict object groups as one object whose properties are the
 // union of the groups. The canonical Zod schema still enforces the exact group.
 const flattenRootObjectUnion = (root) => {
-    const { anyOf, oneOf, ...rest } = root;
+    // [[gemini-sanitize]:inputSchemaPresentation.$schema] not a Gemini protobuf Schema field; removed.
+    const { anyOf, oneOf, $schema: _advertisedSchema, ...rest } = root;
     const alternatives = Array.isArray(anyOf) ? anyOf : oneOf;
     if (!Array.isArray(alternatives) ||
         alternatives.length === 0 ||
@@ -161,11 +162,13 @@ const flattenRootObjectUnion = (root) => {
         ])),
         ...(required.length > 0 ? { required } : {}),
         ...(fewestRequired > 0 ? { minProperties: fewestRequired } : {}),
+        // [[gemini-sanitize]:inputSchemaPresentation.dependentRequired] not a Gemini protobuf Schema field; removed.
+        // The same caller guidance is carried in the description instead.
         ...(Object.keys(dependencies).length > 0
             ? {
-                [root.$schema === "http://json-schema.org/draft-07/schema#"
-                    ? "dependencies"
-                    : "dependentRequired"]: dependencies,
+                description: `${description.length > 0 ? `${description.join(" ")} ` : ""}${Object.entries(dependencies)
+                    .map(([name, partners]) => `${name} requires ${partners.join(", ")}`)
+                    .join("; ")}.`,
             }
             : {}),
         ...(branches.every((branch) => branch.additionalProperties === false)
diff --git a/dist/contracts/compactInputSchema.js b/dist/contracts/compactInputSchema.js
index c609fca..7e85efb 100644
--- a/dist/contracts/compactInputSchema.js
+++ b/dist/contracts/compactInputSchema.js
@@ -197,7 +197,7 @@ const reduceToShallowProperties = (schema) => {
     ]
         .filter((text) => text !== undefined)
         .join(" ");
-    const $schema = typeof schema.$schema === "string" ? { $schema: schema.$schema } : {};
+    // [[gemini-sanitize]:compactInputSchema.$schema] not a Gemini protobuf Schema field; removed.
     const shallow = Object.fromEntries(Object.entries(properties).map(([name, value]) => {
         const types = detectedTypes(value, schema);
         const type = types?.length === 1 ? types[0] : types;
@@ -210,7 +210,7 @@ const reduceToShallowProperties = (schema) => {
         ];
     }));
     return {
-        ...$schema,
+        // [[gemini-sanitize]:compactInputSchema.$schemaSpread] not a Gemini protobuf Schema field; removed.
         type: "object",
         description,
         properties: shallow,
@@ -220,9 +220,8 @@ const reduceToShallowProperties = (schema) => {
         ...(typeof schema.minProperties === "number"
             ? { minProperties: schema.minProperties }
             : {}),
-        ...(isSchemaObject(schema.dependentRequired)
-            ? { dependentRequired: { ...schema.dependentRequired } }
-            : {}),
+        // [[gemini-sanitize]:compactInputSchema.dependentRequired] not a Gemini protobuf Schema field; removed.
+        // The canonical schema still enforces it server-side, so it is not advertised.
         ...(schema.additionalProperties === false
             ? { additionalProperties: false }
             : {}),

Happy to open a PR against the sources if you point me at the right module names 鈥?I only have the compiled package locally.

Originally created by @jzPPP3333 on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2832 ### Summary rea's advertised tool input schemas carry JSON Schema keywords that Google's `functionDeclarations[].parameters` does not accept (`dependentRequired`, `dependencies`, `$schema`). A single offending tool makes the **entire** request fail with HTTP 400, so every tool in the catalog becomes unavailable for the whole session on any Gemini-backed client. ``` Invalid JSON payload received. Unknown name "dependentRequired" at 'request.tools[0].function_declarations[131].parameters': Cannot find field. Status: 400 ``` ### Environment | | | |---|---| | rea-agents | `6.4.0` (npm `latest` is also `6.4.0`, so upgrading does not help) | | Node | `v24.15.0` | | Client | Claude Code `2.1.295` (`claude-desktop-3p`), `POST /v1/messages?beta=true` | | Transport | local Anthropic鈫扜emini gateway, upstream `https://daily-cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse` | | MCP registration | `{"command":"node","args":[".../rea-agents/scripts/rea.mjs","mcp"]}` | | Tools in the failing request | 283 | ### Observed vs expected * **Observed:** 400 from the upstream on every turn. The gateway forwards `tools[].input_schema` into `tools[0].function_declarations[].parameters`; the offending declaration is `mcp__rea__compare_web_captures` at index 131. * **Expected:** a tool catalog that survives translation into the vendors' function-calling dialects, or at least degrades to per-tool unavailability instead of failing the whole request. I isolated the offender by walking the captured request body recursively: of 283 tools exactly **one** contained `dependentRequired`, and its index was exactly the 131 Google named. Notably the same request also carried `$schema` on all 283 tools plus `propertyNames` (index 1) and `oneOf` (index 112). The gateway already strips those, so only `dependentRequired` leaked through 鈥?consistent with a strip-list that predates JSON Schema 2019-09. ### Root cause Two presentation-layer emitters advertise keywords that are not part of Google's protobuf `Schema` message: **1. `dist/contracts/inputSchemaPresentation.js` 鈫?`flattenRootObjectUnion`** When a root-level `anyOf`/`oneOf` of strict object groups is flattened into a single object, the "a field unique to a group still needs that group's required partners" hint is emitted as a schema-map keyword, choosing the keyword by `$schema` dialect: ```js ...(Object.keys(dependencies).length > 0 ? { [root.$schema === "http://json-schema.org/draft-07/schema#" ? "dependencies" : "dependentRequired"]: dependencies, } : {}), ``` Both `dependencies` and `dependentRequired` are unknown to Gemini's `Schema`. The same function also spreads `...rest`, which re-advertises the root `$schema`. **2. `dist/contracts/compactInputSchema.js` 鈫?`reduceToShallowProperties`** ```js const $schema = typeof schema.$schema === "string" ? { $schema: schema.$schema } : {}; ... ...$schema, ... ...(isSchemaObject(schema.dependentRequired) ? { dependentRequired: { ...schema.dependentRequired } } : {}), ``` `$schema` is re-advertised and `dependentRequired` is deliberately preserved through the reduction. (This path is only reachable when the inline form exceeds the byte budget 鈥?easy to miss, since the inline path preserves whatever the caller passed.) ### Minimal reproduction ```js import { presentInputJsonSchema } from "rea-agents/dist/contracts/inputSchemaPresentation.js"; presentInputJsonSchema({ $schema: "https://json-schema.org/draft/2020-12/schema", anyOf: [ { type: "object", properties: { a: { type: "string" }, b: { type: "string" } }, required: ["a"] }, { type: "object", properties: { c: { type: "string" }, d: { type: "string" } }, required: ["c"] }, ], }, (s) => ({ ...s })); ``` Returns (2020-12 鈫?`dependentRequired`; draft-07 鈫?`dependencies`), plus a root `$schema`: ```json { "type": "object", "description": "Provide a complete input group: a + [b]; c + [d].", "properties": { "a": { "type": "string" }, "b": { "type": "string" }, "c": { "type": "string" }, "d": { "type": "string" } }, "minProperties": 1, "dependentRequired": { "b": ["a"], "d": ["c"] }, "additionalProperties": false } ``` ### Impact Any Gemini-backed client (Gemini CLI, Antigravity/Cloud Code gateways, or any Anthropic鈫扜emini bridge) loses the **whole** tool catalog 鈥?not just the one tool 鈥?for as long as the catalog includes a schema with a mutually-exclusive input group. `compare_web_captures` is one such tool today; any future tool whose root is a union of object groups will reintroduce it. ### Suggested fix The advertised form is presentation-only 鈥?`dist/contracts/inputSchemaPresentation.js` itself notes *"The canonical Zod schema still enforces the exact group."* So the hint can move into `description` and the keywords can be dropped: ``` b requires a; d requires c. ``` Options, in order of preference: 1. **Never advertise `dependencies` / `dependentRequired` / `$schema`.** Carry the partner-field guidance in `description` (it is already built right above the emission site). 2. **Gate them on a per-client capability flag** (Anthropic/OpenAI accept them, Google does not) so the union-flattening hint survives where it is legal. Either way, please also assert this in the existing Gemini client verification (`verify:gemini-client`) by validating the advertised catalog against Google's actual `Schema` field set 鈥?see related #1932 / #1935. A cheap guard is to reject any advertised schema keyword that is not in Google's `Schema` message whenever a Gemini client is detected. ### Workaround applied locally Stripped the four emission sites (and re-emitted the partner hint as description text). Verified against the compiled modules: * draft-07 root union, 2020-12 root union, and the forced `reduceToShallowProperties` path all no longer emit `$schema` / `dependencies` / `dependentRequired`; * the hint survives: `"description": "Provide a complete input group: a + [b]; c + [d]. b requires a; d requires c."` <details> <summary>Local patch (dist-level, for reference only)</summary> ```diff diff --git a/dist/contracts/inputSchemaPresentation.js b/dist/contracts/inputSchemaPresentation.js index eb7351f..e3a9236 100644 --- a/dist/contracts/inputSchemaPresentation.js +++ b/dist/contracts/inputSchemaPresentation.js @@ -117,7 +117,8 @@ const inputGroup = (branch) => { // root union of strict object groups as one object whose properties are the // union of the groups. The canonical Zod schema still enforces the exact group. const flattenRootObjectUnion = (root) => { - const { anyOf, oneOf, ...rest } = root; + // [[gemini-sanitize]:inputSchemaPresentation.$schema] not a Gemini protobuf Schema field; removed. + const { anyOf, oneOf, $schema: _advertisedSchema, ...rest } = root; const alternatives = Array.isArray(anyOf) ? anyOf : oneOf; if (!Array.isArray(alternatives) || alternatives.length === 0 || @@ -161,11 +162,13 @@ const flattenRootObjectUnion = (root) => { ])), ...(required.length > 0 ? { required } : {}), ...(fewestRequired > 0 ? { minProperties: fewestRequired } : {}), + // [[gemini-sanitize]:inputSchemaPresentation.dependentRequired] not a Gemini protobuf Schema field; removed. + // The same caller guidance is carried in the description instead. ...(Object.keys(dependencies).length > 0 ? { - [root.$schema === "http://json-schema.org/draft-07/schema#" - ? "dependencies" - : "dependentRequired"]: dependencies, + description: `${description.length > 0 ? `${description.join(" ")} ` : ""}${Object.entries(dependencies) + .map(([name, partners]) => `${name} requires ${partners.join(", ")}`) + .join("; ")}.`, } : {}), ...(branches.every((branch) => branch.additionalProperties === false) diff --git a/dist/contracts/compactInputSchema.js b/dist/contracts/compactInputSchema.js index c609fca..7e85efb 100644 --- a/dist/contracts/compactInputSchema.js +++ b/dist/contracts/compactInputSchema.js @@ -197,7 +197,7 @@ const reduceToShallowProperties = (schema) => { ] .filter((text) => text !== undefined) .join(" "); - const $schema = typeof schema.$schema === "string" ? { $schema: schema.$schema } : {}; + // [[gemini-sanitize]:compactInputSchema.$schema] not a Gemini protobuf Schema field; removed. const shallow = Object.fromEntries(Object.entries(properties).map(([name, value]) => { const types = detectedTypes(value, schema); const type = types?.length === 1 ? types[0] : types; @@ -210,7 +210,7 @@ const reduceToShallowProperties = (schema) => { ]; })); return { - ...$schema, + // [[gemini-sanitize]:compactInputSchema.$schemaSpread] not a Gemini protobuf Schema field; removed. type: "object", description, properties: shallow, @@ -220,9 +220,8 @@ const reduceToShallowProperties = (schema) => { ...(typeof schema.minProperties === "number" ? { minProperties: schema.minProperties } : {}), - ...(isSchemaObject(schema.dependentRequired) - ? { dependentRequired: { ...schema.dependentRequired } } - : {}), + // [[gemini-sanitize]:compactInputSchema.dependentRequired] not a Gemini protobuf Schema field; removed. + // The canonical schema still enforces it server-side, so it is not advertised. ...(schema.additionalProperties === false ? { additionalProperties: false } : {}), ``` </details> Happy to open a PR against the sources if you point me at the right module names 鈥?I only have the compiled package locally.
bsakaguchi 2026-10-15 18:30:19 +00:00
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

Fixed on main in 80de3145e4: input-schema presentation removes dependentRequired/dependencies while retaining conditional validation on the server and guidance for callers.

Rechecked the actual full and compact MCP tools/list wire catalogs at 7375ab6e3: both expose 207 tools, with zero occurrences of either dependency keyword. All 207 input schemas in each profile passed Ajv 2020-12 schema validation and compilation.

This closes the reported dependency-keyword rejection. The 2020-12 dialect declaration remains explicit; the gateway in this report already strips it. Other gateway compatibility and a live authenticated Gemini request were not verified.

<!-- gh-comment-id:6162024550 --> @morluto commented on GitHub (Oct 14, 2026): Fixed on main in 80de3145e458474319f160d2d2c09586e7a282a4: input-schema presentation removes `dependentRequired`/`dependencies` while retaining conditional validation on the server and guidance for callers. Rechecked the actual full and compact MCP `tools/list` wire catalogs at 7375ab6e3: both expose 207 tools, with zero occurrences of either dependency keyword. All 207 input schemas in each profile passed Ajv 2020-12 schema validation and compilation. This closes the reported dependency-keyword rejection. The 2020-12 dialect declaration remains explicit; the gateway in this report already strips it. Other gateway compatibility and a live authenticated Gemini request were not verified.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1011
No description provided.