fix(antigravity): reject caller-supplied tools instead of silently ignoring them - #405
fix(antigravity): reject caller-supplied tools instead of silently ignoring them#405r-uben wants to merge 6 commits into
Conversation
Greptile SummaryThe PR makes
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| src/adapters/antigravity/mod.rs | Adds early validation for unsupported tool requests with tests covering the accepted and rejected request shapes. |
| README.md | Documents the CLI transport’s tool-request refusal behavior and exemptions. |
| site/src/content/docs/guides/providers.mdx | Expands provider guidance to explain why caller-controlled tool loops are unsupported and which alternatives to use. |
| README.ja.md | Adds a Japanese Antigravity CLI section consistent with the English operational and security guidance. |
| README.ko.md | Adds a Korean Antigravity CLI section consistent with the English operational and security guidance. |
| README.zh-CN.md | Adds a Simplified Chinese Antigravity CLI section consistent with the English operational and security guidance. |
Reviews (4): Last reviewed commit: "Merge branch 'main' into fix/404-antigra..." | Re-trigger Greptile
There was a problem hiding this comment.
Code Review
This pull request introduces validation to reject requests carrying caller-supplied tools or unsupported tool_choice values in the Antigravity provider, returning a 400 Bad Request instead of silently ignoring them. Documentation and tests have been updated accordingly. Feedback suggests improving the robustness of the tool_choice check by supporting both the object format {"type": "none"} and the plain string format "none".
| let choice = request.get("tool_choice").filter(|c| !c.is_null()); | ||
| if choice.and_then(|c| c.get("type")).and_then(Value::as_str) == Some("none") { | ||
| return Ok(()); | ||
| } |
There was a problem hiding this comment.
[MEDIUM] Support both object and string formats for "none" tool choice
Problem: The current implementation only exempts tool_choice when it is an object of shape {"type": "none"}. However, some client libraries or API translation layers may normalize or pass tool_choice as a plain string "none" (which is standard in OpenAI-compatible APIs). If a client sends "none" as a string, the request will be rejected even though the caller explicitly requested no tools.
Rationale: Robustness and defensive programming principles. Ensuring compatibility with common client variations prevents unexpected 400 errors.
Suggestion: Update the check to support both the object shape {"type": "none"} and the plain string "none".
| let choice = request.get("tool_choice").filter(|c| !c.is_null()); | |
| if choice.and_then(|c| c.get("type")).and_then(Value::as_str) == Some("none") { | |
| return Ok(()); | |
| } | |
| let choice = request.get("tool_choice").filter(|c| !c.is_null()); | |
| let is_none = choice.and_then(|c| c.get("type")).and_then(Value::as_str) == Some("none") | |
| || choice.and_then(Value::as_str) == Some("none"); | |
| if is_none { | |
| return Ok(()); | |
| } |
References
- Ensure the code behaves as intended and handles edge cases defensively. (link)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c91adac57a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| **Antigravity has two transports.** The `antigravity` provider talks to the Google Antigravity backend over HTTP, authenticated with `shunt login antigravity` — a Google authorization-code flow using Antigravity's own OAuth client and scopes, so a Gemini CLI login cannot be reused for it. It speaks the same Code Assist protocol as the `gemini` provider and currently serves the Gemini-family Antigravity models; the Claude models Antigravity also offers need request rewrites that are not implemented yet (#368). | ||
|
|
||
| **`antigravity-cli` is deprecated and is arbitrary code execution.** It runs the local `agy` binary in agentic mode: the CLI uses its own tools to do the work and shunt streams its progress back as Anthropic SSE. A `tools` array on the request is therefore not forwarded and no `tool_use` block is ever returned. Because a non-interactive run cannot answer a permission prompt, `agy` runs with `--dangerously-skip-permissions`, so **treat this provider as arbitrary code execution as the user running shunt**. Two settings bound it: `sandbox` (default `true`) passes `--sandbox`, which keeps reads and writes inside the workspace and is what actually contains the agent; `workspace_roots` decides only where it may *start*, gating the `Working directory:` path taken from the request's system prompt (client-controlled text) to canonicalized paths under roots you list. Keep the sandbox on and the bind on loopback. Prefer the `antigravity` provider above, which needs none of this. See the [providers guide](https://shunt.dev/guides/providers/). | ||
| **`antigravity-cli` is deprecated and is arbitrary code execution.** It runs the local `agy` binary in agentic mode: the CLI uses its own tools to do the work and shunt streams its progress back as Anthropic SSE. It can therefore never return a `tool_use` block, so a request carrying `tools`, or a `tool_choice` other than `none`, is refused with a `400` rather than silently answered as text. Because a non-interactive run cannot answer a permission prompt, `agy` runs with `--dangerously-skip-permissions`, so **treat this provider as arbitrary code execution as the user running shunt**. Two settings bound it: `sandbox` (default `true`) passes `--sandbox`, which keeps reads and writes inside the workspace and is what actually contains the agent; `workspace_roots` decides only where it may *start*, gating the `Working directory:` path taken from the request's system prompt (client-controlled text) to canonicalized paths under roots you list. Keep the sandbox on and the bind on loopback. Prefer the `antigravity` provider above, which needs none of this. See the [providers guide](https://shunt.dev/guides/providers/). |
There was a problem hiding this comment.
Update the maintained translations with this behavior
This updates the English README and provider guide with the new 400 rejection contract, but the maintained README.{ko,ja,zh-CN}.md and localized ko/ja/zh-cn provider guides remain unchanged, so users of those documented surfaces are not told that tool-bearing requests now fail instead of returning text. Update the corresponding translations in this change as required.
AGENTS.md reference: AGENTS.md:L57-L64
Useful? React with 👍 / 👎.
| let message = "The Antigravity provider cannot use caller-supplied tools. `agy` runs its own \ | ||
| tool set and never returns a tool_use block, so a request carrying `tools` or \ | ||
| `tool_choice` would receive a text-only reply that silently ignores them. Send the \ | ||
| task as a plain prompt and let agy do the work, or route this model to a provider \ | ||
| that forwards tools." |
There was a problem hiding this comment.
Identify the antigravity-cli transport in the error
When a tool-bearing request reaches kind = "antigravity_cli", this message says that the “Antigravity provider” cannot use tools, even though the native antigravity provider is the documented replacement and does forward them. This can mislead an operator into avoiding the working provider; name the deprecated antigravity-cli transport explicitly in the rejection.
Useful? React with 👍 / 👎.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will improve performance by 10.39%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | value_parse[307200] |
1.5 ms | 1.3 ms | +10.39% |
Tip
Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.
Comparing r-uben:fix/404-antigravity-reject-caller-tools (e598e93) with main (83ab560)
There was a problem hiding this comment.
1 issue found across 7 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="README.md">
<violation number="1" location="README.md:154">
P2: Update `README.ja.md`, `README.ko.md`, and `README.zh-CN.md` with the new `antigravity-cli` tool-request refusal behavior. Otherwise those maintained entry points document the old silent-success behavior.</violation>
</file>
Architecture diagram
sequenceDiagram
participant C as Client (Agent Loop)
participant S as Shunt Server
participant A as AntigravityAdapter
participant P as Prompt Extractor
participant AGY as agy Subprocess
participant E as Error Response
C->>S: POST /messages (tools, tool_choice)
S->>A: handle_request(request)
alt NEW: reject_caller_tools() validation
A->>A: Check tool_choice.type
alt tool_choice.type == "none"
A->>A: Allow (exemption)
A->>P: extract_antigravity_prompt()
P->>AGY: Spawn agy CLI
AGY-->>A: Stream text-only response
A-->>C: 200 with text, stop_reason: "end_turn"
else tools array non-empty OR tool_choice exists (not "none")
A->>A: Build 400 invalid_request_error
A->>E: Return AdapterError
E-->>C: 400 BAD_REQUEST + error message
else no tools, no tool_choice (plain prompt)
A->>P: extract_antigravity_prompt()
P->>AGY: Spawn agy CLI
AGY-->>A: Stream text-only response
A-->>C: 200 with text, stop_reason: "end_turn"
end
end
Note over C,E: Failure path: Claude Code subagent sends tools without tool_choice
C->>S: POST /messages (tools: [...], no tool_choice)
S->>A: handle_request(request)
A->>A: tools non-empty, no tool_choice
A->>E: Reject with 400
E-->>C: 400 invalid_request_error
Note over C: Subagent receives clear error instead of hanging
Note over C,E: Failure path: tool_choice: {"type": "any"} even without tools
C->>S: POST /messages (tool_choice: {"type": "any"})
S->>A: handle_request(request)
A->>A: tool_choice.type == "any"
A->>E: Reject with 400
E-->>C: 400 invalid_request_error
Note over C: Violates Messages contract that must produce tool_use block
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| **Antigravity has two transports.** The `antigravity` provider talks to the Google Antigravity backend over HTTP, authenticated with `shunt login antigravity` — a Google authorization-code flow using Antigravity's own OAuth client and scopes, so a Gemini CLI login cannot be reused for it. It speaks the same Code Assist protocol as the `gemini` provider and currently serves the Gemini-family Antigravity models; the Claude models Antigravity also offers need request rewrites that are not implemented yet (#368). | ||
|
|
||
| **`antigravity-cli` is deprecated and is arbitrary code execution.** It runs the local `agy` binary in agentic mode: the CLI uses its own tools to do the work and shunt streams its progress back as Anthropic SSE. A `tools` array on the request is therefore not forwarded and no `tool_use` block is ever returned. Because a non-interactive run cannot answer a permission prompt, `agy` runs with `--dangerously-skip-permissions`, so **treat this provider as arbitrary code execution as the user running shunt**. Two settings bound it: `sandbox` (default `true`) passes `--sandbox`, which keeps reads and writes inside the workspace and is what actually contains the agent; `workspace_roots` decides only where it may *start*, gating the `Working directory:` path taken from the request's system prompt (client-controlled text) to canonicalized paths under roots you list. Keep the sandbox on and the bind on loopback. Prefer the `antigravity` provider above, which needs none of this. See the [providers guide](https://shunt.dev/guides/providers/). | ||
| **`antigravity-cli` is deprecated and is arbitrary code execution.** It runs the local `agy` binary in agentic mode: the CLI uses its own tools to do the work and shunt streams its progress back as Anthropic SSE. It can therefore never return a `tool_use` block, so a request carrying `tools`, or a `tool_choice` other than `none`, is refused with a `400` rather than silently answered as text. Because a non-interactive run cannot answer a permission prompt, `agy` runs with `--dangerously-skip-permissions`, so **treat this provider as arbitrary code execution as the user running shunt**. Two settings bound it: `sandbox` (default `true`) passes `--sandbox`, which keeps reads and writes inside the workspace and is what actually contains the agent; `workspace_roots` decides only where it may *start*, gating the `Working directory:` path taken from the request's system prompt (client-controlled text) to canonicalized paths under roots you list. Keep the sandbox on and the bind on loopback. Prefer the `antigravity` provider above, which needs none of this. See the [providers guide](https://shunt.dev/guides/providers/). |
There was a problem hiding this comment.
P2: Update README.ja.md, README.ko.md, and README.zh-CN.md with the new antigravity-cli tool-request refusal behavior. Otherwise those maintained entry points document the old silent-success behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 154:
<comment>Update `README.ja.md`, `README.ko.md`, and `README.zh-CN.md` with the new `antigravity-cli` tool-request refusal behavior. Otherwise those maintained entry points document the old silent-success behavior.</comment>
<file context>
@@ -151,7 +151,7 @@ xAI may gate OAuth access by subscription tier — if `grok` returns 403, use th
**Antigravity has two transports.** The `antigravity` provider talks to the Google Antigravity backend over HTTP, authenticated with `shunt login antigravity` — a Google authorization-code flow using Antigravity's own OAuth client and scopes, so a Gemini CLI login cannot be reused for it. It speaks the same Code Assist protocol as the `gemini` provider and currently serves the Gemini-family Antigravity models; the Claude models Antigravity also offers need request rewrites that are not implemented yet (#368).
-**`antigravity-cli` is deprecated and is arbitrary code execution.** It runs the local `agy` binary in agentic mode: the CLI uses its own tools to do the work and shunt streams its progress back as Anthropic SSE. A `tools` array on the request is therefore not forwarded and no `tool_use` block is ever returned. Because a non-interactive run cannot answer a permission prompt, `agy` runs with `--dangerously-skip-permissions`, so **treat this provider as arbitrary code execution as the user running shunt**. Two settings bound it: `sandbox` (default `true`) passes `--sandbox`, which keeps reads and writes inside the workspace and is what actually contains the agent; `workspace_roots` decides only where it may *start*, gating the `Working directory:` path taken from the request's system prompt (client-controlled text) to canonicalized paths under roots you list. Keep the sandbox on and the bind on loopback. Prefer the `antigravity` provider above, which needs none of this. See the [providers guide](https://shunt.dev/guides/providers/).
+**`antigravity-cli` is deprecated and is arbitrary code execution.** It runs the local `agy` binary in agentic mode: the CLI uses its own tools to do the work and shunt streams its progress back as Anthropic SSE. It can therefore never return a `tool_use` block, so a request carrying `tools`, or a `tool_choice` other than `none`, is refused with a `400` rather than silently answered as text. Because a non-interactive run cannot answer a permission prompt, `agy` runs with `--dangerously-skip-permissions`, so **treat this provider as arbitrary code execution as the user running shunt**. Two settings bound it: `sandbox` (default `true`) passes `--sandbox`, which keeps reads and writes inside the workspace and is what actually contains the agent; `workspace_roots` decides only where it may *start*, gating the `Working directory:` path taken from the request's system prompt (client-controlled text) to canonicalized paths under roots you list. Keep the sandbox on and the bind on loopback. Prefer the `antigravity` provider above, which needs none of this. See the [providers guide](https://shunt.dev/guides/providers/).
**Migrating from the old `antigravity`.** `kind = "antigravity"` used to mean the local CLI. A config still carrying that meaning is refused by name rather than silently retargeted, and a routed `antigravity` provider with no credential refuses to start — switching transport, credentials, and egress underneath a green startup would be worse than failing. Run `shunt login antigravity`, or point the route at `antigravity-cli`.
</file context>
|
Thanks — the review caught a real defect. Pushed Accepted
The error named the wrong thing (codex P2, cubic P3). It said "The Antigravity provider Docs overstated the rule (cubic P3). Corrected in all four locales: an empty Local gate after the change: fmt and clippy clean, 1698 tests pass (up 3), 0 failures. Declined, with reasoningAccept Update The same holds for Where a translated surface does document this, I did update it: the Happy to open a follow-up issue for the missing translated Antigravity sections if that is |
|
Filed the declined translation finding as #412 rather than leaving it as a refusal in a comment. To be explicit about the split: the reference tables in |
…opping them
The CLI transport runs `agy`, which resolves its own tool calls internally and
has no mode that hands them back to the caller. `tools` and `tool_choice` were
therefore never read: a request carrying them got a 200 whose stop_reason is
`end_turn` and which contains only text.
An agentic caller cannot act on that. A Claude Code subagent stalls waiting for
a tool call that will never arrive, and `tool_choice: {"type": "any"}` — which
the Messages contract says must produce a tool_use block — is violated outright
while still returning success.
Fail closed with a 400 invalid_request_error naming the limitation, so the
caller learns why on the first turn rather than hanging. `tool_choice` of
`none` is exempt: the caller has declared it does not want tool calls, so a
text-only answer is what it asked for.
Refs pleaseai#404
The guide, the reference table and the README all described the old behavior — tools "not forwarded", no tool_use block — which was accurate but read as a quiet degradation rather than a refusal. State the actual contract: a `tools` array or a `tool_choice` other than `none` is now a 400, and say why (agy owns its tool loop, so no tool_use can be emitted). Reference table updated in all four locales. The README translations do not carry the antigravity-cli paragraph at all, so there is nothing to keep in sync there. Refs pleaseai#404
… call Review found the first cut over-broad: it refused any `tool_choice` other than `none`, which caught `auto` with no tools. `auto` is the Anthropic SDK default and many clients serialize it on every request, so an ordinary text prompt that never wanted a tool would have started failing with a 400. Narrow the rule to what genuinely obliges a `tool_use` block: a non-empty `tools` array, or a `tool_choice` of `any`/`tool`. `none` stays exempt even alongside `tools`, and `auto` with no tools is now exempt too. Also name the transport in the error. It said "The Antigravity provider cannot use caller-supplied tools", but the native `antigravity` provider forwards them fine — only the deprecated `antigravity-cli` cannot. An operator reading the old text could switch away from the one that works. The message now names `antigravity-cli` and points at the alternatives. Docs in all four locales corrected to match the narrowed rule, including that an empty `tools: []` is not a tool request. Refs pleaseai#404
71a1dd0 to
ffddf0c
Compare
# Conflicts: # README.ja.md # README.ko.md # README.zh-CN.md
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Summary
The
antigravity_clitransport never readtoolsortool_choice. A request carryingthem got a
200with a text-only body andstop_reason: "end_turn"— including when thecaller sent
tool_choice: {"type": "any"}, which the Messages contract says must produce atool_useblock.Callers that drive their own agent loop cannot act on that response. In practice a Claude
Code subagent goes idle with no result, or dies with a confusing mid-response server error.
Nothing told the caller the provider could not serve its tools.
This fails the request closed instead, with a
400 invalid_request_errornaming thelimitation.
Why not forward the tools instead
agyresolves its own tool calls internally and has no mode that hands them back.agy --help(1.1.15) exposes only--dangerously-skip-permissionsand the--input-format/--output-formatstream-json pair — there is no "emit tool requestsinstead of executing them" mode to translate. So this is not "add tool support"; it is
"stop reporting success for a request we silently degraded".
Callers that need a real tool loop should use the
antigravityHTTP provider orgemini,both of which translate
toolsandtool_choiceproperly.The one judgement call
tool_choice: {"type": "none"}is exempt. The caller has declared it does not want toolcalls, so a text-only answer is exactly what it asked for; refusing it would break callers
that pass a tool list they never intend the model to use.
Everything else is refused, including a bare
toolsarray with notool_choice— which isthe shape Claude Code subagents actually send, and therefore the shape that has to fail
loudly for this fix to be worth anything. An empty
tools: []carries no capabilityrequest and is not refused.
Test plan
Five unit tests in
src/adapters/antigravity/mod.rs:caller_tools_without_a_choice_are_rejected— the reported shapea_forcing_tool_choice_is_rejected—tool_choice: anywith notoolstool_choice_none_is_allowed_even_alongside_tools— the exemptionan_empty_tools_array_is_not_a_tool_request— no false positivea_plain_prompt_is_untouched— the ordinary pathLocal gate green:
cargo fmt --all --checkclean,cargo clippy --all-targets --all-features -- -D warningsclean,cargo test --all-features --workspace1695 passed /0 failed plus every integration suite.
Reproduced against a live gateway before and after, on
gemini-3.6-flashandgemini-3.1-pro.Docs
README.mdandsite/src/content/docs/guides/providers.mdx— both described the oldquiet-drop behavior; now state the refusal and the
noneexemption.site/src/content/docs/reference/configuration.mdand itsko/ja/zh-cncopies — thekindrow gains the same clause.antigravity-cliparagraph at all, so there wasnothing to keep in sync there.
Note on the deprecated transport
This touches a transport the project has deprecated in favour of the
antigravityHTTPprovider, which is normally an argument against spending review on it. Worth flagging that
the HTTP path is not universally usable yet: on my Antigravity subscription it returns
429 Resource has been exhaustedfor every model, including a bare prompt with no tools,while the
agyCLI path serves the same models on the same Google account at the samemoment. Auth and project discovery both succeed — Google just refuses the quota. So the
deprecated transport is still the working one for at least some subscriptions, and a silent
hang is a poor thing to leave them on.
Closes #404
Summary by cubic
Rejects caller-supplied tools in the
antigravity_clitransport and fails closed with a 400 invalid_request_error. Previously it silently droppedtools/tool_choiceand returned a text-only 200 (stop_reason: "end_turn"), which broke agent loops and violatedtool_choice: {"type":"any"}. Closes #404.toolsarray ortool_choiceofany/tool; acceptstool_choice: {"type":"none"},tool_choice: {"type":"auto"}with no tools, andtools: [].antigravity-cliand points callers toantigravity/gemini; unit tests cover refusal and exemptions; README and guides/reference (en/ja/ko/zh-cn) document the rule.Migration
antigravityorgemini; otherwise send a plain prompt or settool_choice: {"type":"none"}.tool_choice: {"type":"auto"}with no tools andtools: []are accepted.Written for commit e598e93. Summary will update on new commits.