Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "antigravity",
"version": "0.22.4",
"version": "0.22.5",
"description": "Run the Antigravity CLI (Gemini) as a collaborating AI inside Claude Code, with intelligent model routing across the SDLC. Claude conducts — requirements, architecture, the hard 20%, verification, review — and routes deterministic, high-volume work (scaffolding, tests, first-pass review, migrations, web/Vertex AI Search) to Antigravity. Hybrid agentic engineering; lower token cost as a financial lever.",
"author": {
"name": "linyuting"
Expand Down
95 changes: 95 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,101 @@
All notable changes to **Antigravity for Claude Code**. Format loosely follows
[Keep a Changelog](https://keepachangelog.com/); versions are in `.claude-plugin/plugin.json`.

## 0.22.5
- **`doctor` now asks agy which model it will run, instead of inferring it from a version
string.** 0.22.4 added a warning for agy below 1.1.10, where `--model` was ignored in
headless `-p`. That warning is the best a version comparison can do, and a version
comparison is a proxy: it is right about the releases we know about and silent about
every other way the flag can fail to land. agy 1.1.11 answers the read-only slash
commands in print mode without starting an agent turn, so `doctor` requests a tier model
with `-p /model` and reports which one comes back — `usage.total_tokens: 0`, no quota
spent, no conversation left behind. It reads the tab-separated reply's slug and matches
it against a tier configured as a display name, the same either-direction comparison
`agy models` needed in 0.20.x.
**Gated at 1.1.11 and tested as a fact, not as prose.** Below that version the slash
command is not recognised, falls through as literal prompt text, and the model answers as
though it had run — so probing there would spend a real turn *and* then trust the answer
it invented. The stub agy in the suite logs every invocation, so "never probes below
1.1.11" is asserted against the log. An empty answer draws no conclusion in either
direction: an older build than the version claims, a hang, or a plan that refuses the
probe is not evidence that routing is broken.
- **`doctor` validates `permissions.allow` entries.** The plugin recommends such a rule in
eight places as the *narrow* alternative to `--yolo`, and the recommendation ships a
placeholder — `write_file(<dir>)`. A rule agy cannot parse announces itself in neither
direction, and which way it fails depends on the version: from **1.1.11** it matches
nothing, so the grant is absent and the write is soft-denied (exit 15) with the rule
sitting visibly in the file; **before 1.1.11** an entry that tokenized to zero command
words matched **every** command and silently auto-approved anything the agent ran —
broader than the `--yolo` it was chosen instead of. `doctor` flags the entry, names the
reason, and scopes the consequence **twice**: to your agy version, and to the reason.
Every class means the grant is absent; only a `command(...)` rule naming no command also
carries the match-everything history, so that sentence is printed only when such an entry
is actually present. A mistyped `write_file()` never had it, and putting a security claim
in front of someone holding one would be worse than saying less. (Both reviewers caught
the first cut attaching it to every finding.)
The zero-command-word test follows upstream's own examples (`command(time)` — a shell
reserved word that prefixes a command without being one — a comment-only entry, and an
empty compound `()`), plus the unsubstituted `<...>` placeholder, which is ours — matched
as that *shape*, not as a bare angle bracket, so a rule carrying a literal redirect
(`command(echo hi > /tmp/f)`) is not misread as a template nobody filled in. Rules it
cannot judge are left alone: unbalanced quotes are agy's parser's business, and
`write_file(...)` is a different matcher from `command(...)`. A false positive here sends
someone to edit a rule that was always fine, so the well-formed case is pinned as hard as
the broken ones.
- **Verified against agy 1.1.11, no change needed:** all three tiers still resolve
(`flash` → `gemini-3.5-flash-high`, `flash-lo` → `-low`, `pro` → `gemini-3.1-pro-high`);
the exit-14 classifier still fires on an unknown model; and 1.1.11's reworded
model-loading errors do not disturb the exit-15 path, whose message still contains
`auto-denied` and `permissions.allow` verbatim — two independent anchors the classifier
already matches.
**`/usage` and `/quota` were considered for `doctor` and rejected on evidence:** they
return zero bytes here, which is not a bug but this account type — 1.1.11's own notes say
credits do not apply to accounts signed in through a Google Cloud project or ADC.
`/model`, `/effort` and `/skills` all return data on the same setup, which is what made
the probe above possible.
- **The suite had a false-negative construct in 25 assertions, and this release woke it
up.** `printf '%s' "$x" | grep -q PAT` is the shape fixed in 0.21.1 and explained in a
comment in this very file: `grep -q` exits at the first match and closes the pipe, the
writer dies of SIGPIPE (141), and `set -o pipefail` marks the whole pipeline failed — so
the assertion reads "not found" while the text is right there. The window is whatever the
writer still has to emit *after* the matched line, which is why it survives review and
why adding output below a match can revive it: the 1.1.9 version-gate assertion started
failing 1–4 times in 8 concurrent runs on this branch and never on master.
Diagnosed rather than guessed. Instrumenting the *test* made it vanish (it changes the
timing), so the trace went inside `doctor` instead: `mktemp` never failed and `AGY_VER`
was never empty, which left only the assertion misreading output that had in fact been
printed. All 25 now use a `case`-based `has()` — same test, no second process, no pipe —
and the five `sed … | grep -q` source scans give `grep` a process substitution instead,
so `sed`'s death is no longer `pipefail`'s business. Those five failed **silently**: a
false negative there reads as "the property holds", and among them are the guards for
the delegate wrapper's `--help` probe, `agy_guard`'s pipe rule, and the `sort -V`
dependency. 24 consecutive clean runs at the concurrency that reproduced it; each
converted scan re-checked by mutation.
**That fix then shipped the same defect in a new shape, and both reviewers caught it.**
`has()` was defined beside the doctor tests, above which two call sites already sat —
and bash does not hoist, so those two were `command not found`, exit 127, `else` taken
unconditionally, `ok` printed regardless. One of them was the "json envelope leaked to
stdout" assertion, which had already been voided once before by a different accident.
Helpers now live at the top of the file, and `tests/check-helper-order.py` runs first
and fails the suite if any function is called above its definition. bash 4's
`command_not_found_handle` was tried for this and **removed**: macOS ships bash 3.2,
where merely defining it is a silent no-op — a guard that reads as protection and
provides none, which is the defect this whole entry is about. The static check works on
any shell and was verified by putting the original bug back: it names the call site and
the definition line. Its first regex missed `elif`, a call inside a `case` branch, and a
brace group — all three confirmed against the old pattern — so it now splits the line
into command segments and compares each segment's first word, with no list of contexts
to keep complete. The checker has its own tests, including a no-false-positive case,
because a guard that misses a shape is the defect it exists to prevent. It also flagged
its own test data, which is fair: a fixture written across real lines is not
distinguishable from code, so the fixtures are single-line now.
- **The exit-15 message now points at the rule itself.** It is the one place someone
actually lands when a write is soft-denied, and it recommended `permissions.allow`
without allowing that the rule *is* the problem: "if a rule is ALREADY in that file and
you are still reading this, suspect the rule: run `agy-doctor`". Same caveat added to
`agents/antigravity-delegate.md` and `commands/delegate.md`, the two operational files
consulted while building a delegation call. The pre-run write nudge got the short half only — `<dir>` is a placeholder, `agy-doctor` will say whether yours parses — because that one fires on every write-looking task, while the diagnosis belongs where the failure is.

## 0.22.4
- **`--tier` did nothing on agy below 1.1.10, and nothing said so.** agy 1.1.10 fixed
`--model` and `--effort` being *ignored in headless `-p`* — the flag was applied after
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,13 @@ Delegation doesn't save money by itself — these do (also in the skill):
- **`permissions.allow` in `~/.gemini/antigravity-cli/settings.json`** — a
`write_file(<dir>)` entry allows writes **recursively beneath `<dir>`** and needs no
flag. This is the narrower grant and usually the right one.
**`<dir>` is a placeholder — substitute a real path.** A rule agy cannot parse says
nothing in either direction: from **agy 1.1.11** it matches nothing, so the grant you
think you have is absent and the write is soft-denied for no visible reason; **before
1.1.11** an entry that tokenized to zero command words (upstream's examples:
`command(time)`, a comment-only entry, `()`) matched **every** command and silently
auto-approved anything the agent ran — broader than the `--yolo` it was chosen instead
of. `agy-doctor` checks your entries and names which failure applies to your version.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph is about the write_file(<dir>) placeholder specifically, but the "before 1.1.11 ... matched every command" consequence it describes is scoped to command(...)-shaped rules with zero words — per scripts/doctor.sh's own bad_allow_rules() (and the PR's second commit message: "A mistyped write_file(<dir>) never had it. Telling someone holding one that their config auto-approves everything is a security claim about a situation they are not in.").

bad_allow_rules() classifies an unsubstituted <dir> placeholder as "unparseable", not "zerowords" — only "zerowords" (the command(time)/comment-only/() examples) carries the match-everything history, and doctor.sh prints that sentence only when a zerowords entry is present. As written, this bullet tells a reader whose write_file(<dir>) was never substituted that pre-1.1.11 it "matched every command and silently auto-approved anything the agent ran" — which, per the code's own distinction, isn't true for that matcher.

Same overclaim is copy-pasted into docs/TROUBLESHOOTING.md:113-118, docs/POC-PLAYBOOK.md:106-111, skills/antigravity/SKILL.md:220-225, and (shorter form) agents/antigravity-delegate.md:89-92. commands/delegate.md avoids it and reads correctly.

- **`--yolo`** (`--dangerously-skip-permissions`) — auto-approves **all** tools, not just
writes. Needed when no rule covers the target, and for web / Vertex AI Search / terminal
tools.
Expand Down
4 changes: 3 additions & 1 deletion agents/antigravity-delegate.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ Options: `--tier flash|flash-lo|pro` · `--dir <repo-root>` (so agy reads
write needs a grant. Pass `--yolo` unless the user has a `permissions.allow`
`write_file(<dir>)` rule covering the target in `~/.gemini/antigravity-cli/settings.json`
— that grants the write recursively beneath `<dir>` with no flag, and is narrower than
`--yolo`, which approves every tool. You cannot see that file, so `--yolo` stays the
`--yolo`, which approves every tool. If they say a rule is in place and the write is
still soft-denied, have them run `agy-doctor` before anything else: an entry agy cannot
parse grants nothing, and before agy 1.1.11 it granted everything. You cannot see that file, so `--yolo` stays the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This restates the pre-1.1.11 "granted everything" consequence for any unparseable permissions.allow entry, not just the command(...)-naming-no-command (zerowords) class. scripts/doctor.sh's bad_allow_rules deliberately does not do this — per its own comment and the commit history on this PR ("Both reviewers on #56 caught the first cut printing the pre-1.1.11 … consequence for every flagged entry … A mistyped write_file(<dir>) never had it"), only a command(...) rule tokenizing to zero words carries that history. write_file(<dir>) — the exact placeholder shipped throughout the docs — is a different matcher and was never shown to match-everything pre-1.1.11.

README.md, docs/TROUBLESHOOTING.md, docs/POC-PLAYBOOK.md and skills/antigravity/SKILL.md all scope this correctly ("an entry that tokenized to zero command words … matched every command"), but this file still makes the generic claim. Since this is the guidance the delegate subagent itself reads before telling a user their rule "granted everything," it reintroduces the exact overreach the linked commit says was fixed.

Same issue at scripts/agy-delegate.sh:497 (the exit-15 message users actually see).

default; if a run comes back exit `15`, the allow-rule is the smaller fix. Either way tell
the caller to run on a dedicated branch/worktree and review the diff before merging.
- **Read-only** (analysis, first-pass review, search): no `--yolo` needed unless
Expand Down
3 changes: 2 additions & 1 deletion commands/delegate.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ Do this:
**If the task WRITES files or uses tools** (web / Vertex AI Search / terminal), it needs
a grant. For a plain file write the narrower one is a `write_file(<dir>)` entry under
`permissions.allow` in `~/.gemini/antigravity-cli/settings.json` (recursive beneath
`<dir>`, no flag needed). Otherwise pass **`--yolo`**, which auto-approves all tools and
`<dir>`, no flag needed — substitute a real path for `<dir>`; if a rule is already
there and the write is still denied, `agy-doctor` checks whether agy can parse it). Otherwise pass **`--yolo`**, which auto-approves all tools and
is what web / Vertex AI Search / terminal need. Without a grant,
headless agy leaves your workspace untouched while still reporting success (it
describes / scratch-diverts / soft-denies depending on version; issue #10). `--mode
Expand Down
7 changes: 7 additions & 0 deletions docs/POC-PLAYBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ After each lever: rerun the task → rerun the gate → keep only if quality hel
- **`permissions.allow` in `~/.gemini/antigravity-cli/settings.json`** — a
`write_file(<dir>)` entry allows writes **recursively beneath `<dir>`** and needs no
flag. This is the narrower grant and usually the right one.
**`<dir>` is a placeholder — substitute a real path**, and run `agy-doctor` before a
measured run. A rule agy cannot parse is silent in both directions: from **agy
1.1.11** it matches nothing, so the grant is absent and the write is soft-denied with
the rule sitting visibly in the file; **before 1.1.11** an entry naming no command
(`command(time)`, a comment-only entry, `()`) matched **every** command and silently
auto-approved anything the agent ran. Either failure makes an arm's write behaviour a
second uncontrolled variable — exactly what §5 says a cost claim cannot carry.
- **`--yolo`** (`--dangerously-skip-permissions`) — auto-approves **all** tools, not just
writes. Needed when no rule covers the target, and for web / Vertex AI Search / terminal
tools.
Expand Down
8 changes: 8 additions & 0 deletions docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ run still "succeeds"** ([#10](https://github.com/yuting0624/antigravity-for-clau
This is the rule agy's own soft-deny message is naming. Confirmed on agy 1.1.9 by a
controlled A/B ([#37](https://github.com/yuting0624/antigravity-for-claude-code/issues/37));
a glob form (`write_file(/path/**)`) was reported *not* to match.
**Substitute a real path for `<dir>`** — and if the rule is in place and the write is
*still* soft-denied, suspect the rule before suspecting agy. An entry agy cannot parse
is silent both ways: from **1.1.11** it matches nothing (so you get exactly this
exit 15, with the rule sitting right there in the file), and **before 1.1.11** an entry
that tokenized to zero command words — `command(time)`, a comment-only entry, `()` —
matched **every** command and silently auto-approved anything the agent ran. Run
`agy-doctor`: it validates each entry and tells you which of the two applies to your
version.
- **Or pass `--yolo`** (`--dangerously-skip-permissions`) — works across all agy versions,
but auto-approves **all** tools, not just the write. Required anyway for web / Vertex AI
Search / terminal when no rule covers them. (`--mode accept-edits` only wrote headless on
Expand Down
4 changes: 2 additions & 2 deletions scripts/agy-delegate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ if [ "$YOLO" -eq 0 ] && [ "$PRINT_CMD" -ne 1 ]; then
shopt -s nocasematch
case "$PROMPT" in
*implement*|*scaffold*|*migrate*|*refactor*|*"write the file"*|*"create the file"*|*"edit the file"*)
echo "agy-delegate: note: this looks like a write task and --yolo is not set. Headless agy will NOT touch your workspace without a write grant (it describes / scratch-diverts / soft-denies depending on version, while the run still 'succeeds'; issue #10). Two grants work: a permissions.allow rule matching the target — write_file(<dir>), a recursive prefix, in ~/.gemini/antigravity-cli/settings.json — which is the narrower one and needs no flag; or --yolo, which auto-approves ALL tools. If a rule already covers your target, ignore this. Otherwise add one, or pass --yolo on a dedicated branch, and verify with git status." >&2 ;;
echo "agy-delegate: note: this looks like a write task and --yolo is not set. Headless agy will NOT touch your workspace without a write grant (it describes / scratch-diverts / soft-denies depending on version, while the run still 'succeeds'; issue #10). Two grants work: a permissions.allow rule matching the target — write_file(<dir>), a recursive prefix, in ~/.gemini/antigravity-cli/settings.json — which is the narrower one and needs no flag; or --yolo, which auto-approves ALL tools. If a rule already covers your target, ignore this — but <dir> is a placeholder, and agy-doctor will tell you whether yours actually parses. Otherwise add one, or pass --yolo on a dedicated branch, and verify with git status." >&2 ;;
esac
shopt -u nocasematch
fi
Expand Down Expand Up @@ -494,7 +494,7 @@ if [ -z "${OUT//[$' \t\n\r']/}" ]; then
*"auto-denied"*|*"permissions.allow"*|*"permission that headless"*|*"dangerously-skip-permissions"*)
shopt -u nocasematch
[ -s "$ERR" ] && cat "$ERR" >&2
echo "agy-delegate: agy soft-denied a tool that needs permission (headless can't prompt) — no work was done. For a FILE WRITE, the narrower fix is a permissions.allow rule covering the target in ~/.gemini/antigravity-cli/settings.json — write_file(<dir>) matches recursively beneath <dir> — which needs no flag; --yolo also works but auto-approves ALL tools. Other tools (web / Vertex AI Search / terminal) need --yolo unless a rule covers them. agy's own message above names the specific permission it wanted. (agy >= 1.1.3)" >&2
echo "agy-delegate: agy soft-denied a tool that needs permission (headless can't prompt) — no work was done. For a FILE WRITE, the narrower fix is a permissions.allow rule covering the target in ~/.gemini/antigravity-cli/settings.json — write_file(<dir>) matches recursively beneath <dir> — which needs no flag; --yolo also works but auto-approves ALL tools. Other tools (web / Vertex AI Search / terminal) need --yolo unless a rule covers them. agy's own message above names the specific permission it wanted. If a rule is ALREADY in that file and you are still reading this, suspect the rule: run agy-doctor, because an entry agy cannot parse grants nothing (and before agy 1.1.11 granted everything). (agy >= 1.1.3)" >&2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same over-generalization as agents/antigravity-delegate.md:92: "an entry agy cannot parse … before agy 1.1.11 granted everything" applies the match-everything history to any unparseable entry, but doctor.sh's bad_allow_rules only attributes it to the zerowords class (a command(...) rule naming no command). A mistyped write_file(<dir>) — the placeholder this very message tells the user to use — is a different matcher and, per the doctor.sh comment, "never had it." This is the exact scoping bug the commit history says both reviewers already caught and fixed in doctor.sh's own output; it just didn't get propagated to this string, which is what a user actually sees at the point of an exit-15 failure.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good addition here, but it lands in only one of the two write-grant messages in this file. The proactive nudge at line 256 ("this looks like a write task and --yolo is not set...") recommends the exact same write_file(<dir>) rule and is fired before the run — arguably the more natural place to plant "if a rule is already there and it's still not working, run agy-doctor" — but it wasn't touched.

The CHANGELOG entry for this release explicitly scopes the "suspect the rule" caveat to three places (this exit-15 message, agents/antigravity-delegate.md, commands/delegate.md), so this looks like an intentional-but-incomplete propagation rather than an oversight — worth either adding the same one-liner to the line-256 nudge, or confirming the omission is deliberate (e.g. because that message fires pre-emptively and doctor can't have run yet).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exit-15 message still says an unparseable permissions.allow entry "grants nothing (and before agy 1.1.11 granted everything)" — attaching the pre-1.1.11 "matched EVERY command" history to any parse failure, including the write_file(<dir>) placeholder named earlier in the same sentence.

Per bad_allow_rules() in scripts/doctor.sh (added in this PR), that history belongs only to the zerowords class (command(...) naming no command); a mistyped write_file() is unparseable and per the CHANGELOG "never had it." agents/antigravity-delegate.md:92-93 and skills/antigravity/SKILL.md:222-225 both carry the corrected, scoped wording ("do not attach that history to a mistyped write_file()"), and this PR's own fixup commit swept "five documents" to remove exactly this conflation — this .sh file (not a "document") looks like it was missed and still has the pre-fixup phrasing from the first commit.

No test pins this exact string, so scoping the parenthetical to the command(...)-naming-no-command case (like the two docs above) should be safe.

signal PERMISSION_DENIED "agy soft-denied a permissioned tool in headless — add a permissions.allow rule or pass --yolo"; exit 15 ;;
esac
shopt -u nocasematch
Expand Down
Loading
Loading