Skip to content

Commit 7a5a4d0

Browse files
akshayliveclaude
andauthored
fix(antigravity): enforce tool lists, non-interactive commands, bounded polling (#215)
* fix(antigravity): enforce tool lists, non-interactive commands, bounded polling Upgrade google-antigravity to 0.1.20 and close three Antigravity gaps that made its runs diverge from Claude Code and Codex: - allowed_tools / disallowed_tools are now mapped onto the harness builtin tools (Bash->run_command, Read->view_file, ...) and passed as CapabilitiesConfig; finish always stays on, subagents follow Task. - run_command gets CI=1, npm_config_yes, GIT_TERMINAL_PROMPT=0, DEBIAN_FRONTEND=noninteractive, PIP_NO_INPUT=1 and PAGER=cat unless the environment already sets them. - Only an orphaned run_command keeps the turn polling, and the wait is capped at 10 minutes (or 80% of turn_timeout if shorter). Orphaned edit_file / view_file / start_subagent calls no longer hold the turn. On a 370-task SkillSpec run this cut wall time from 174 to 75 minutes and poll-budget hangs from 21 (24 min each) to 3 (10 min each, all servers the model left running). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(deps): bump urllib3 2.8.0 and virtualenv 21.14.2 to clear pip-audit Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 33bc3d7 commit 7a5a4d0

7 files changed

Lines changed: 438 additions & 73 deletions

File tree

‎.claude/notes/agents.md‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,10 @@ agent had produced; bounding it only by a fixed cycle count disconnected from `t
434434
path unreachable — the watchdog always wins, and the same spurious-orphan turn burns the
435435
full turn timeout before crashing with zero criteria evaluated.
436436

437-
The cycle cap is the SOLE bound when a task sets no timeout at all. It is deliberately not
437+
The cycle cap applies alongside that deadline, whichever comes first, and is the SOLE
438+
bound when a task sets no timeout at all. Under a long `turn_timeout` (1800 s gives a
439+
1440 s deadline) the deadline alone let a never-ending command (a dev server, an
440+
unanswered prompt) idle the turn for 24 minutes. It is deliberately not
438441
"break after N consecutive empty polls": `receive_steps()` returns identically empty
439442
whether a backgrounded job is still running or will never resolve, and there is no signal
440443
that tells the two apart except waiting. A count small enough to matter would abort real
@@ -447,6 +450,36 @@ answer in a headless eval), CANCELED and UNKNOWN — none of which the closed se
447450
done, and none of which the poll loop should wait out, since they will never become DONE
448451
on their own.
449452

453+
It is also an allowlist on the TOOL: only a `run_command` can be backgrounded. Any other
454+
tool left ACTIVE never resolved in practice: an `edit_file` on the read-only skill mount,
455+
a `view_file` paged at a large `content_offset`, a `start_subagent`. In a 370-task
456+
SkillSpec run, 12 of 21 turns that polled to the deadline were waiting on one of those,
457+
after the model had already finished. `finalize` force-closes them as unresolved.
458+
459+
## Antigravity tool allowlist
460+
461+
`allowed_tools` / `disallowed_tools` map onto the harness's builtin toolset
462+
(`CapabilitiesConfig.enabled_tools` / `disabled_tools`) through
463+
`_CLAUDE_TO_ANTIGRAVITY_TOOL_MAP`, so an experiment's allowlist confines Antigravity the
464+
way it confines Claude Code. Without it Antigravity ran with every builtin, including
465+
`start_subagent` and `search_web`, under a list that gave Claude Code neither. `Skill` and
466+
`TodoWrite` have no builtin (skills load through `skills_paths`) and are skipped. `finish`
467+
stays on under any allowlist: it returns structured output, not a capability.
468+
`enable_subagents` is a separate switch from the toolset and follows whether
469+
`start_subagent` survives. The SDK takes an allowlist OR a denylist, so with both set the
470+
denied tools are removed from the allowlist.
471+
472+
## Antigravity non-interactive commands
473+
474+
`run_command` runs in a real terminal: a command that blocks becomes a background task the
475+
model can check on and type into. A command that stops to ask (`npx` installing a missing
476+
package, git credentials, an apt/pip confirmation, a pager) and that the model then leaves
477+
behind keeps the turn waiting on a prompt no one answers. `_NONINTERACTIVE_ENV`
478+
(`CI`, `npm_config_yes`, `GIT_TERMINAL_PROMPT=0`, `DEBIAN_FRONTEND`, `PIP_NO_INPUT`,
479+
`PAGER`/`GIT_PAGER=cat`) rides the per-agent `env` seam, each variable only where the
480+
environment does not already set it, so those commands answer themselves or fail fast. It
481+
cannot close the terminal: a bare `read` or a server still runs until the poll cap.
482+
450483
## The receive_steps re-entrancy window
451484

452485
`receive_steps()` is two nested async generators: the public one delegates to the

‎docs/agents/ANTIGRAVITY.md‎

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ working directory — both required for an unattended eval.
2727
pip install 'coder-eval[antigravity]'
2828
```
2929

30-
This pulls in `google-antigravity` (pinned to `0.1.18`), whose wheel bundles the
30+
This pulls in `google-antigravity` (pinned to `0.1.20`), whose wheel bundles the
3131
platform `localharness` binary. As with the other agents the SDK is imported lazily
3232
— a base install without the extra still runs end-to-end; Antigravity tasks fail at
3333
dispatch with a clear hint to install the extra.
@@ -136,18 +136,28 @@ can't be resolved or if zero skills are discovered.
136136

137137
## Permissions & tools — important differences
138138

139-
**Antigravity ignores `permission_mode`, `allowed_tools`, and `disallowed_tools`.**
140-
The local harness runs in a single unconditional mode: every tool call (including
141-
`run_command`) is approved via an allow-all policy, and file tools are restricted to
142-
the configured `workspaces` (the sandbox working directory plus any skill roots).
139+
**Antigravity ignores `permission_mode`.** The local harness runs in a single
140+
unconditional mode: every tool call (including `run_command`) is approved via an
141+
allow-all policy, and file tools are restricted to the configured `workspaces` (the
142+
sandbox working directory plus any skill roots).
143+
144+
**`allowed_tools` / `disallowed_tools` are enforced** by mapping the Claude tool names
145+
onto the harness's builtin tools (`Bash` → `run_command`, `Read` → `view_file`, `Write` →
146+
`create_file`, `Edit` → `edit_file`, `Glob` → `find_file`, `Grep` → `search_directory`,
147+
`Task` → `start_subagent`, `WebSearch` → `search_web`, `WebFetch` → `read_url_content`).
148+
`Skill` has no builtin and is skipped; `finish` always stays on.
149+
150+
`run_command` also gets non-interactive environment variables (`CI=1`,
151+
`npm_config_yes=true`, `GIT_TERMINAL_PROMPT=0`, `DEBIAN_FRONTEND=noninteractive`,
152+
`PIP_NO_INPUT=1`, `PAGER=cat`), each only where the environment does not already set it.
143153

144154
The trust boundary for an Antigravity run is therefore the **sandbox**, not the
145155
agent config. Run untrusted tasks under the [Docker driver](../DOCKER_ISOLATION.md);
146156
the `tempdir` driver is not a security boundary. This mirrors the reality that the
147157
`bypassPermissions`-equivalent behavior is always on for this backend.
148158

149-
Those inherited fields still exist on the config for schema uniformity but have no
150-
runtime effect here — don't rely on them to gate Antigravity.
159+
`permission_mode` still exists on the config for schema uniformity but has no runtime
160+
effect here — don't rely on it to gate Antigravity.
151161

152162
## Telemetry
153163

@@ -187,19 +197,18 @@ as every other agent.
187197
4. **`permission_mode` does not confine the harness.** Every mode runs
188198
`policy.allow_all()`; coder_eval's write boundary is the sandbox driver, and a
189199
headless eval has no human to approve anything.
190-
5. **`allowed_tools` / `disallowed_tools` are not read.** The harness runs with its
191-
full builtin tool set, so an Antigravity run has tools (web search, subagents,
192-
URL fetch) that the same task file denies on Claude Code and Codex.
193-
6. **`max_turns` is counted by the harness.** One `communicate()` is a single SDK turn
200+
5. **`max_turns` is counted by the harness.** One `communicate()` is a single SDK turn
194201
here, so the harness counts model API calls itself (a MODEL step at a new
195202
`step_index` opens one) and enforces the cap on the step loop. See
196203
[Run-Limit Parity](HARNESS_PARITY.md).
197-
7. **Shell commands over ~10s are moved to the background.** The localharness has a
204+
6. **Shell commands over ~10s are moved to the background.** The localharness has a
198205
10-second maximum synchronous wait; past it the command becomes a background task
199206
and the model gets a task id, not a result. The turn polls for that result instead
200-
of finalizing on an idle step stream, so slow work does complete — but the wait is
201-
bounded by 80% of `turn_timeout`, and a job that outlives it is force-closed as
202-
`result_status: unknown` and graded as an ordinary low score rather than a timeout.
207+
of finalizing on an idle step stream, so slow work does complete — but only an
208+
orphaned `run_command` is waited on, the wait is bounded by 10 minutes or 80% of
209+
`turn_timeout` (whichever is shorter), and a job that outlives it (typically a server
210+
the model left running) is force-closed as `result_status: unknown` and graded
211+
normally rather than as a timeout.
203212
Measured in [Run-Limit Parity](HARNESS_PARITY.md).
204213

205214
## Running in Docker

‎docs/agents/HARNESS_PARITY.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -718,9 +718,10 @@ both. See [OpenCode](OPENCODE.md) and [Pi § plugins](PI.md#known-limitations).
718718
lowercase (`bash`/`read`/`write`/`edit`/`grep`/`find`/`ls`), but the shared config
719719
default (`experiments/default.yaml`) sets Claude-namespaced names
720720
(`Bash`/`Read`/`Write`/…). Forwarding those to `--tools` would allowlist tools that
721-
do not exist in Pi and strip the agent of ALL tools — so, like OpenCode (drops them),
722-
Codex (forwards `disallowed_tools` without SDK enforcement), and Antigravity (does not
723-
read them), Pi ignores them and runs with its full native toolset. A task that needs a
721+
do not exist in Pi and strip the agent of ALL tools — so, like OpenCode (drops them)
722+
and Codex (forwards `disallowed_tools` without SDK enforcement), Pi ignores them and
723+
runs with its full native toolset. (Antigravity maps them onto its builtin tools; see
724+
[Antigravity](ANTIGRAVITY.md).) A task that needs a
724725
restricted Pi toolset would have to name Pi's lowercase tools — a documented follow-up.
725726
- **`permission_mode` is NOT enforced** — Pi headless print mode auto-runs tools and
726727
exposes only project-file trust (`--approve` / `--no-approve`), no tool-approval

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ codex = [
131131
# Without this extra the framework still installs and runs; Antigravity-dependent
132132
# code paths fail at start() with a clear hint pointing back here.
133133
antigravity = [
134-
"google-antigravity==0.1.18",
134+
"google-antigravity==0.1.20",
135135
]
136136
# Optional extra that enables OpenCode agent support.
137137
#

0 commit comments

Comments
 (0)