fix(antigravity): stop the OAuth client scan from eating a neighbouring string - #242
Merged
Merged
Conversation
…ng string `scan_client_ids` in `crates/tb_core_ffi/src/agent_antigravity.rs` locates a Google OAuth client id by finding the `.apps.googleusercontent.com` suffix and walking backward over token bytes (`[A-Za-z0-9_-]`) to the start of the id. In the shipped Antigravity `language_server` binary — a ~139 MB packed Mach-O — the bytes immediately in front of a client id belong to whatever string was laid down next to it, with no separator. The greedy walk-back absorbs that neighbour's tail into the id's head. A Google client id is `<digits>-<token>`, and `valid_client_id` requires the head before the first `-` to be all digits. With a neighbour's tail glued on, the head contains letters, so every id is rejected and `scan_client_ids` returns an empty vector. `scan_client_secrets` is unaffected because it reads a fixed-length window forward from its `GOCSPX-` prefix rather than walking back. Effect: with Antigravity installed but not running, the OAuth route — which reads the `.app` from disk via `discover_client_from_app` and does not need the IDE process — could not resolve a client at all, and the card showed "Antigravity OAuth client was not found" even though both ids were present in the binary. The live-IDE path was unaffected because it never calls this scan. Fix: after the walk-back, re-anchor on the first `-` in the candidate and keep only the digit run immediately before it, trimming any non-digit neighbour bytes off the numeric head without disturbing the id. Ceiling (recorded in the regression test): a neighbour whose own tail is digits is indistinguishable from the id's numeric head, so those digits are kept. Nothing in the byte stream marks the boundary; the only stronger fix is parsing Mach-O string sections, which is disproportionate given Google's 12-digit ids. The existing `scans_and_pairs_oauth_client_from_bytes` test missed this because its fixture separates the id with `\x00`, which is not a token byte, so the walk-back stopped on its own — the anchor made the defect unreachable. The new `scans_client_id_glued_to_a_neighbouring_string` test packs the id against a neighbour with no separator and fails (`left: []`) when the fix is reverted, matching the observed `ids=0` on a real install. Verified: `cargo test -p tb_core_ffi agent_antigravity` (27 passed); `cargo clippy -p tb_core_ffi --all-targets` (27 warnings before and after, none new); and on a real install with the IDE closed, `swift run TokenBar --smoke` moves the Antigravity error from "OAuth client was not found" to "token refresh was rejected" — the client now resolves and the route reaches the credential stage (the remaining failure is a separately-stale ~/.gemini/oauth_creds.json).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ceb2195799
ℹ️ 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".
Addresses a P2 from Codex review on the scan fix. The re-anchor step chose the first `-` in the walked-back candidate and kept the digit run before it. A Google client id is `<digits>-<token>` with exactly one hyphen — the token and the `.apps.googleusercontent.com` suffix carry none — so when a neighbour's packed tail ends in `…<letters><digits>-`, the first hyphen is the neighbour's, not the id's. Because `valid_client_id` only checks that the digits before the *first* hyphen are numeric, that produced a fabricated-but-accepted id: `label123-beta456-real. apps…` yielded `123-beta456-real.apps…` (head `123`, accepted) instead of the real `456-real.apps…`. In the installed-but-closed IDE path that id is submitted to Google's token endpoint and the refresh fails, defeating the fix for such artifact layouts. Re-anchor on the last hyphen (`rposition`) instead. Since the id's token is hyphen-free, the last hyphen is always the id's delimiter, and the maximal digit run before it is the project number. This is strictly more correct than the first-hyphen choice: it recovers the real id in both the single-neighbour-hyphen case and the multi-hyphen case, and leaves the documented ceiling unchanged — a neighbour whose digits are glued directly onto the project number with no intervening hyphen still merges into the head, because nothing in the byte stream marks that boundary. New assertion in `scans_client_id_glued_to_a_neighbouring_string` locks Codex's counterexample: it yields `456-real.apps…` and fails with the fabricated `123-beta456-real.apps…` when `rposition` is reverted to `position`. Verified: `cargo test -p tb_core_ffi agent_antigravity` (27 passed); `cargo clippy -p tb_core_ffi --all-targets` (27 warnings before and after); real install with the IDE closed, `swift run TokenBar --smoke` still reports `token refresh was rejected` — the two single-hyphen ids in the shipped binary resolve exactly as before, confirming this only changes the multi-hyphen case.
Nanako0129
added a commit
that referenced
this pull request
Aug 25, 2026
Replace the stale v1.14.1 override notes with v1.14.2's, covering the Antigravity "quota when the IDE is closed" work. `scripts/release_notes.sh` ships `release-notes.override.txt` verbatim to the Sparkle dialog / appcast / latest.json and `release-notes.override.md` verbatim as the GitHub Release body, so these files are the source of truth for this release's notes; DeepSeek generation is skipped when they are present. The deterministic "Thanks: @…" line (txt) and GitHub's "New Contributors" / "Full Changelog" tail (md) are still appended automatically from PR authorship, which credits @iF2007 for #236; the md also carries an explicit per-change credit. Change set since v1.14.1: - #236 (@iF2007): fetch Antigravity quota when the IDE is closed or not installed — OAuth remote plus an `agy` CLI fallback. - #242: fix the OAuth client scan absorbing a neighbouring value in the installed language_server binary, which returned zero client ids and disabled the OAuth route for an installed-but-closed IDE. No literal <, &, or > in either file, so the appcast CDATA/HTML escaping path is not exercised.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
With Antigravity installed but not running, the Antigravity card shows
Antigravity OAuth client was not found. Install Antigravity.app or configure its OAuth client.— even though the app is installed and the OAuth client id/secret are present in itslanguage_serverbinary.The OAuth route does not need the IDE process:
discover_client_from_appreads the.appartifacts from disk withstd::fs::read. So a closed-but-installed IDE should resolve a client and fetch quota. It did not, and the cause is in our scan, not in the environment.Root cause
scan_client_idsfinds the.apps.googleusercontent.comsuffix, then walks backward over token bytes ([A-Za-z0-9_-]) to locate the start of the id. In the shippedlanguage_server(a ~139 MB packed Mach-O), the bytes immediately in front of a client id belong to whatever string was laid down next to it, with no separator. The walk-back absorbs that neighbour's tail into the id's head.A Google client id is
<digits>-<token>, andvalid_client_idrequires the head before the first-to be all digits. With a neighbour's tail glued on, the head contains letters, so every id is rejected and the scan returns an empty vector — whilescan_client_secrets, which reads a fixed-length window forward from itsGOCSPX-prefix, is unaffected.Measured on a real install (
/Applications/Antigravity.app/.../bin/language_server): both client ids present in the binary were rejected,scan_client_idsreturned0, andresolve_oauth_clientreturnedNone— taking the entire OAuth route down whenever the IDE was not running.The existing unit test did not catch this because its fixture separates the id with a
\x00byte. NUL is not a token byte, so the walk-back stops on its own — the fixture's anchor made the defect unreachable.Fix
After the greedy walk-back, re-anchor on the first
-in the candidate and keep only the digit run immediately before it. This trims any non-digit neighbour bytes back off the numeric head without disturbing the id itself.Known ceiling, recorded in the test: if a neighbour's own tail is digits, it is indistinguishable from the id's numeric head and those digits are kept. Nothing in the byte stream marks the boundary. The only stronger fix is parsing Mach-O string sections instead of scanning bytes, which is disproportionate for a case that Google's 12-digit ids make rare.
Validation
cargo test -p tb_core_ffi agent_antigravityscans_client_id_glued_to_a_neighbouring_stringleft: []when the fix is reverted (mutation-checked), matching the real-worldids=0cargo clippy -p tb_core_ffi --all-targetsswift run TokenBar --smokeOAuth client was not foundtotoken refresh was rejected— i.e. the client now resolves and the route reaches the credential stageThe remaining
token refresh was rejectedon that machine is a stale~/.gemini/oauth_creds.json(expired refresh token), which is outside this change: it confirms discovery now succeeds and hands off to the refresh step correctly.Scope
Discovery only. This is independent of #236 (which adds an
agyCLI fallback for the not-installed case); it fixes the installed-but-closed case that should already work. No behaviour change for users whose IDE is running — that path never calls this scan.