Repository navigation
Conversation
…-ai#1975) Add programmatic PreToolUse hook support for the agy CLI (successor to Gemini CLI) while keeping the existing Antigravity IDE rules-file integration intact. Changes: - hook_cmd.rs: process_antigravity_payload + run_antigravity() handle both run_command (CommandLine field) and Bash (command field) tool payloads; extra args (e.g. Cwd) are preserved in the rewritten response - init.rs: run_agy_cli_mode(), patch_agy_hooks_json(), agy_hook_already_present(), insert_agy_hook_entry(), uninstall_agy_cli() — install/uninstall hooks.json at ~/.agents/hooks.json (global) or .agents/hooks.json (project-local) - main.rs: AgentTarget::Agy, HookCommands::Antigravity (alias: agy), routing for rtk init --agent agy and rtk hook antigravity; uninstall dispatch updated - constants.rs: ANTIGRAVITY_DIR, ANTIGRAVITY_HOOK_COMMAND, ANTIGRAVITY_TOOL_MATCHER - hooks/antigravity/README.md: document agy CLI vs Antigravity IDE vs Gemini (legacy) JSON protocol: Input: {"toolCall": {"name": "run_command", "args": {"CommandLine": "git status"}}} Output: {"decision": "allow", "toolCall": {"args": {"CommandLine": "rtk git status"}}} No output for non-shell tools or commands with no RTK rewrite (pass-through). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… compat) floor_char_boundary is stable since Rust 1.87 but the Homebrew toolchain does not expose it. Inline the equivalent scan using is_char_boundary which has been stable since Rust 1.0. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rmat - Write hook config to ~/.gemini/antigravity-cli/settings.json (not ~/.agents/hooks.json) - Use BeforeTool/matcher format with per-tool entries for run_command and Bash - Handle both agy payload formats: simple (tool_name/tool_input) and rich (toolCall.args) - Simple payloads respond with hookSpecificOutput.tool_input; rich preserve all extra args - Add AGY_CLI_SUBDIR constant; suppress dead_code on ANTIGRAVITY_DIR Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
agy runs hooks outside the user's shell, so bare `rtk` is not on PATH. Resolve current_exe() at init time and write the full binary path (e.g. /home/user/.cargo/bin/rtk hook antigravity) so the hook is always found. Re-init strips stale relative-path entries before inserting the new absolute-path command. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
agy (Antigravity CLI) fires the run_shell_command BeforeTool hook from ~/.gemini/settings.json but sends a different payload than Gemini CLI: command is at toolCall.args.CommandLine, not tool_input.command. The handler was returning pass-through for every agy call. Detect the rich format first (toolCall.args.CommandLine present), rewrite the command, and return toolCall.args with CommandLine updated while preserving Cwd, WaitMsBeforeAsync, and any other fields. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ite format
Fixes the agy hook integration by addressing two root causes found via
binary analysis of the agy binary:
1. Config target: agy reads hooks from ~/.gemini/config/hooks.json (named
hook format), not from ~/.gemini/antigravity-cli/settings.json (settings
format). Rewrote init.rs to write the correct named-hook structure:
{ "rtk-antigravity": { "enabled": true, "PreToolUse": [...] } }
2. Response format: PreToolHookResult expects "overwrite" (HookToolCall) not
"toolCall" to modify a tool call. Confirmed via agy binary protobuf symbols.
Updated hook_cmd.rs to output:
{"overwrite": {"name": "run_command", "args": {"CommandLine": "rtk ..."}}}
Hooks are now confirmed firing and rewriting commands (verified via agy
cli.log showing hook execution and the prior error message identifying the
field name mismatch).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ials
agy interprets empty hook stdout as "deny". The hook was previously
outputting nothing for non-shell tools (Read, ListPermissions, etc.),
causing those tool calls to be blocked.
Fix: always write at least "{}" (allow, no modification) for any tool
call that doesn't need a command rewrite. Confirmed by live testing where
Read and ListPermissions were denied by our hook before this change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
agy's PreToolHookResult.allow_tool (proto field 1, bool) defaults to
false — an empty {} is an implicit deny, not a pass-through. Every
response must include {"allowTool":true} to permit tool execution.
Also adds allowTool:true alongside overwrite responses so rewritten
shell commands are not blocked by the missing allow flag.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ough
agy's PreToolHookResult semantics:
- {"overwrite": {...}} → replace the tool call (implicitly allowed)
- {"allowTool": true} → allow the original call unchanged
- {"allowTool": true, "overwrite": {...}} → allowTool wins, overwrite ignored
Previously: {} for passthrough → implicit deny (allow_tool defaults to false)
Previously: {allowTool+overwrite} → overwrite silently dropped
Now: overwrite-only for rewrites, allowTool-only for passthrough/skip/ignore.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the hook rewrites git status → rtk git status, agy's permission system checks whether rtk is in the allowed-commands list. Without the grant, the rewritten command is denied even though the original command would have been approved by the user. rtk init -g --agent agy now: - Writes the PreToolUse hook entry into ~/.gemini/config/hooks.json - Adds command(rtk) to permissions.allow in ~/.gemini/antigravity-cli/settings.json rtk init -g --uninstall --agent agy reverts both. Also adds RTK_HOOK_DEBUG=1 support in run_antigravity() for future protocol debugging (dumps raw stdin to ~/.local/share/rtk/agy-hook-debug.log). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The PreToolHookResult overwrite mechanism is broken under agy's toolPermission=request-review setting. When the hook returns an overwrite, the permission review fires on the original binary (git, cargo, etc.) rather than the overwritten one (rtk), so the model never sees the rewritten command. Evidence: turnGrants=[command(git)] on user approval of overwritten command. Switch to deny-with-reason: deny the original command with a denyReason that names the exact rtk-prefixed replacement. The model reads the reason and retries with e.g. 'rtk git status', which hits the Allow list (command(rtk)) directly and bypasses the request-review gate entirely. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove duplicated test_claude_md_mode_refuses_malformed_block introduced during conflict resolution across cherry-picks. Apply cargo fmt output. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
denyReason always shows ⚠ warning in every permission mode. Under toolPermission=always-proceed there is no review gate, so overwrite works transparently: no warning, no extra LLM call, command silently rewritten to rtk-prefixed equivalent. Rich format preserves extra args (Cwd, WaitMsBeforeAsync, etc.) from the original toolCall.args so metadata is not lost on rewrite. Under request-review, overwrite is bypassed by the review gate (original command runs without RTK), but the user is not blocked. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
overwrite alone still hits allowTool=false (proto default), which agy treats as a denial and shows ⚠. Must set allowTool:true explicitly for agy to execute the rewritten command without showing a warning. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Debug logging confirmed: hook sends correct overwrite payload but agy executes the original command unchanged. Only allowTool and denyReason are honored by agy's hook system. denyReason: model reads the replacement command and retries with rtk prefix; command(rtk) in the allow list auto-approves the retry in always-proceed mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds first-class support for Google Antigravity CLI (agy) by introducing a dedicated hook command and installer, while also hardening UTF-8 boundary handling in a couple of string-slicing utilities.
Changes:
- Add a new
Agyagent target andrtk hook antigravityhook command path. - Implement agy CLI install/uninstall logic that patches
~/.gemini/config/hooks.jsonand updates agy permissions insettings.json. - Replace
floor_char_boundaryusages with manual UTF-8 boundary adjustment loops to avoid panics.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main.rs | Adds Agy agent + hook subcommand wiring and uninstall dispatch updates. |
| src/hooks/init.rs | Implements agy CLI init/uninstall, hooks.json patching, and permission allow-list updates. |
| src/hooks/hook_cmd.rs | Adds run_antigravity hook handler and extends Gemini hook to detect a “rich” payload shape. |
| src/hooks/constants.rs | Introduces new constants for agy CLI subdir/layout. |
| src/cmds/system/pipe_cmd.rs | Avoids UTF-8 slicing panic by adjusting to char boundaries manually. |
| src/cmds/system/json_cmd.rs | Avoids UTF-8 slicing panic when truncating long strings. |
| hooks/antigravity/README.md | Updates documentation to describe Antigravity IDE vs agy CLI integration. |
| rtk init -g --agent agy | ||
| ``` | ||
|
|
||
| **Install (project-local):** |
| let mut settings: serde_json::Value = if path.exists() { | ||
| let content = fs::read_to_string(path) | ||
| .with_context(|| format!("Failed to read {}", path.display()))?; | ||
| serde_json::from_str(&content).unwrap_or(serde_json::json!({})) |
| let mut hooks: serde_json::Value = if path.exists() { | ||
| let content = fs::read_to_string(path) | ||
| .with_context(|| format!("Failed to read {}", path.display()))?; | ||
| serde_json::from_str(&content).unwrap_or(serde_json::json!({})) |
| "enabled": true, | ||
| "PreToolUse": [ | ||
| { | ||
| "toolNameMatcher": "^(run_command|Bash)$", |
| // Use the absolute path so agy can find the binary regardless of its PATH. | ||
| let rtk_bin = std::env::current_exe() | ||
| .context("Failed to resolve rtk binary path")? | ||
| .to_string_lossy() | ||
| .to_string(); | ||
| let hook_command = format!("{} hook antigravity", rtk_bin); |
| let dir = dirs::home_dir()?.join(".local").join("share").join("rtk"); | ||
| let mut f = std::fs::OpenOptions::new() | ||
| .append(true) | ||
| .open(dir.join("agy-hook-debug.log")) | ||
| .ok()?; | ||
| let _ = writeln!(f, ">>> {}\n", response); |
| pub fn run_agy_cli_mode(global: bool, ctx: InitContext) -> Result<()> { | ||
| let InitContext { dry_run, verbose } = ctx; |
- Fix debug log OpenOptions missing create(true); response log silently
failed if file did not exist after the earlier input-logging block
- Error instead of silent unwrap_or({}) when settings.json or hooks.json
contains invalid JSON; corrupt config no longer silently overwritten
- Quote rtk binary path in hook_command so spaces in install paths work
- Expand toolNameMatcher to ^(run_command|Bash|bash)$ to match parser
- Add 8 unit tests: file-missing, no-op/idempotent, invalid-JSON error,
and lowercase-bash matcher coverage for agy install/uninstall paths
- Update hooks/antigravity/README.md to document global-only install,
actual file paths, and denyReason protocol (removes stale .agents/ refs)
- Add agy CLI row to main README Quick Start and Supported AI Tools table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Resolves #1975 |
|
This would be very useful. any idea on when it might be resolved? |
|
Closing in favour of #3711, which adds the same native Antigravity PreToolUse hook on the canonical antigravity target. Both of the current Antigravity pull requests rewrite the command through the |
Summary
rtk hook antigravity— aPreToolUsehook for the Google Antigravity CLI (agy) that rewrites bare shell commands to theirrtk-prefixed equivalents before the model executes themrtk init --agent agyconfigures~/.gemini/antigravity-cli/settings.jsonwith the hook and grantscommand(rtk)permission so retries are auto-approvedtool_name/tool_input) and rich (toolCall.name/toolCall.args) agy payload formatsRTK_HOOK_DEBUG=1logs full request/response pairs to~/.local/share/rtk/agy-hook-debug.logHow it works
agy's
overwritefield inPreToolHookResultis silently ignored (confirmed via debug logging — correct payload sent, original command executed). The hook therefore uses thedenyReasonstrategy:run_command/Bashtool calls{"denyReason": "RTK: use 'rtk git log' instead of 'git log' (60-90% token savings)"}rtk-prefixed commandcommand(rtk)in the allow list auto-approves the retry — no user promptCommands already prefixed with
rtkor in the passthrough list pass through with{"allowTool": true}.Test plan
cargo test --all— 1924 tests pass, zero clippy warningsrtk init --agent agywrites correct~/.gemini/antigravity-cli/settings.jsongit logwith denyReason namingrtk git logrtk git logwithallowTool: trueread_file) withallowTool: trueRTK_HOOK_DEBUG=1writes input/output to debug logKnown limitation
agy does not honour the
overwritefield inPreToolHookResult— onlyallowToolanddenyReasonare implemented. The deny-and-retry pattern produces a⚠ Tool call deniednotice in the agy UI before the model retries with the RTK command. Transparent rewriting will be possible once agy implementsoverwritesupport.This issue reported in this topic on discuss.ai.google.dev. More visibility on this bug report required.