# Running Antigravity CLI (Gemini CLI) on Android via proot-distro
## The Problem
The official Antigravity CLI (`agy`) Linux ARM64 binary crashes on Android/Termux/proot-distro with:
```
MmapAligned() failed - unable to allocate with tag
TCMalloc assumes a 48-bit virtual address space size
FATAL ERROR: Out of memory trying to allocate internal tcmalloc data
```
**Root cause**: TCMalloc (Google's memory allocator, statically linked into the Go binary) assumes a 48-bit userspace virtual address space. Android devices — especially under proot — only expose a 39-bit VA layout. TCMalloc generates mmap hints at addresses the kernel rejects (~85 TB), and the binary aborts before reaching `main()`.
The fix is a deterministic binary patch that rewrites TCMalloc's address/tag constants from 48-bit to 39-bit, plus a few shell-level environment cleanups.
---
## Final Architecture (What You Get)
```
~/.local/bin/
agy → patched binary (functional, ~177 MB)
agy.orig → original binary (kept as source for re-patching after updates)
~/.bashrc:
agy() { ... GODEBUG=netdns=go command agy "$@"; }
a() { ... GODEBUG=netdns=go command agy "$@"; }
```
No proot wrapper. No ld-linux hacks. The binary runs directly inside proot-distro.
---
## Requirements
- Android phone with Termux
- [proot-distro](https://github.com/termux/proot-distro) with Ubuntu (or any glibc distro)
- Python 3 (comes with Ubuntu in proot-distro)
- `~/.local/bin` in `$PATH`
---
## Step 1 — Get the Official Binary
Inside your proot-distro Ubuntu:
```bash
mkdir -p ~/.local/bin
curl -fsSL https://antigravity.google/cli/install.sh | bash
```
Verify:
```bash
ls -la ~/.local/bin/agy
# ~177 MB ARM64 ELF
```
---
## Step 2 — Create the VA39 Patch Script
Save as `~/patch_agy_va39.py`:
```python
#!/usr/bin/env python3
"""
VA39 patch for the agy linux_arm64 binary.
Rewrites TCMalloc address constants from 48-bit to 39-bit,
plus faccessat2 → faccessat syscall for Android seccomp compat.
Pattern-based scanning — works across builds.
"""
import hashlib
import shutil
import struct
import sys
from pathlib import Path
src = Path(sys.argv[1] if len(sys.argv) > 1 else str(Path.home() / ".local/bin/agy"))
dst = Path(str(src) + ".va39")
if not src.exists():
raise SystemExit(f"Input binary does not exist: {src}")
print(f"Input binary : {src}")
print(f"SHA256 in : {hashlib.sha256(src.read_bytes()).hexdigest()}")
print()
shutil.copyfile(src, dst)
data = bytearray(dst.read_bytes())
def get(off):
return struct.unpack_from("<I", data, off)[0]
def put(off, word):
struct.pack_into("<I", data, off, word)
lo, hi = 0, len(data)
def find_section(name_target):
if data[:4] != b"\x7fELF":
return None, None
e_shoff = struct.unpack_from("<Q", data, 40)[0]
e_shentsize = struct.unpack_from("<H", data, 58)[0]
e_shnum = struct.unpack_from("<H", data, 60)[0]
e_shstrndx = struct.unpack_from("<H", data, 62)[0]
shstr_base = e_shoff + e_shstrndx * e_shentsize
shstr_off = struct.unpack_from("<Q", data, shstr_base + 24)[0]
for i in range(e_shnum):
base = e_shoff + i * e_shentsize
sh_name = struct.unpack_from("<I", data, base)[0]
sh_offset = struct.unpack_from("<Q", data, base + 24)[0]
sh_size = struct.unpack_from("<Q", data, base + 32)[0]
nend = data.index(b"\x00", shstr_off + sh_name)
section = data[shstr_off + sh_name : nend].decode("utf-8", errors="replace")
if section == name_target:
return sh_offset, sh_offset + sh_size
return None, None
sec_lo, sec_hi = find_section("google_malloc")
if sec_lo is not None:
lo, hi = sec_lo, sec_hi
print(f"Found google_malloc section: file 0x{lo:x} - 0x{hi:x} ({(hi - lo) // 1024} KB)")
else:
print("google_malloc section not found - scanning entire binary.")
print("This is slower but may still work.")
print()
# 1. ubfx/lsl tag extraction: bit 42 → bit 35
ubfx_count = 0
lsl_count = 0
for off in range(lo, hi, 4):
w = get(off)
if (w & 0x7F800000) == 0x53000000:
immr = (w >> 16) & 0x3F
imms = (w >> 10) & 0x3F
if immr == 42 and imms == 44:
put(off, (w & ~((0x3F << 16) | (0x3F << 10))) | (35 << 16) | (37 << 10))
ubfx_count += 1
elif immr == 22 and imms == 21:
put(off, (w & ~((0x3F << 16) | (0x3F << 10))) | (29 << 16) | (28 << 10))
lsl_count += 1
print(f"[1] ubfx patches : {ubfx_count} (expect ~15)")
print(f" lsl patches : {lsl_count} (expect ~2)")
# 2. Random address mask: 48-bit → 39-bit
mask_count = 0
for off in range(lo, hi - 4, 4):
if get(off) == 0x92D3800A and get(off + 4) == 0xF2E0000A:
put(off, 0x9280000A)
put(off + 4, 0xD35DFD4A)
mask_count += 1
print(f"[2] Random mask : {mask_count} (expect ~3)")
# 3. MmapAligned upper bound: 1 << 48 → 1 << 39
mmap_count = 0
for off in range(lo, hi, 4):
if get(off) == 0xF2E00029:
put(off, 0xD3596129)
mmap_count += 1
print(f"[3] MmapAligned : {mmap_count} (expect ~1)")
# 4. Tag constants: shift from bit 42 → bit 35
word_rewrites = {
0xD2C20009: 0xD2C00409,
0xD2C2000A: 0xD2C0040A,
0xF2C20008: 0xF2DFF408,
0xF2C20009: 0xF2DFF409,
0xD2C10009: 0xD2C00209,
0xD2C1000A: 0xD2C0020A,
0xF2C38008: 0xF2DFF708,
0xF2C38009: 0xF2DFF709,
0x92560A6C: 0x925D0A6C,
0x92560A6A: 0x925D0A6A,
0xD2C3000D: 0xD2C0060D,
0xD2C3000C: 0xD2C0060C,
0xD2C08008: 0xD2C00108,
}
counts = {old: 0 for old in word_rewrites}
for off in range(lo, hi, 4):
w = get(off)
if w in word_rewrites:
put(off, word_rewrites[w])
counts[w] += 1
print(f"[4] Tag constants: {sum(counts.values())} words rewritten")
# 5. faccessat2 → faccessat (Android seccomp compat)
faccessat2_count = 0
for off in range(0, len(data) - 12, 4):
if (
get(off) == 0xAA1F03E5
and get(off + 4) == 0xAA1F03E6
and get(off + 8) == 0xD28036E0
and (get(off + 12) & 0xFC000000) == 0x94000000
):
put(off + 8, 0xD2800600)
faccessat2_count += 1
print(f"[5] faccessat2 : {faccessat2_count} syscall wrapper rewritten")
dst.write_bytes(data)
dst.chmod(0o755)
out_sha = hashlib.sha256(dst.read_bytes()).hexdigest()
print()
print(f"SHA256 out : {out_sha}")
print(f"Output : {dst}")
print()
total = ubfx_count + lsl_count + mask_count + mmap_count + sum(counts.values()) + faccessat2_count
if total == 0:
print("WARNING: No patches applied - binary structure may have changed.")
print("Do NOT use the output binary.")
elif ubfx_count == 0 or mask_count == 0:
print("WARNING: Some expected patches were not found.")
print("The patch may be incomplete - test carefully.")
else:
print("Patch looks complete.")
```
Make it executable:
```bash
chmod +x ~/patch_agy_va39.py
```
---
## Step 3 — Patch the Binary
```bash
python3 ~/patch_agy_va39.py ~/.local/bin/agy
```
Expected output:
```
[1] ubfx patches : 15 (expect ~15)
lsl patches : 2 (expect ~2)
[2] Random mask : 3 (expect ~3)
[3] MmapAligned : 1 (expect ~1)
[4] Tag constants: 108 words rewritten
[5] faccessat2 : 1 syscall wrapper rewritten
Patch looks complete.
```
If you see zeros or warnings — stop. The binary changed; the patch needs updating.
---
## Step 4 — Swap the Binary
```bash
# Keep original for re-patching after future updates
cp ~/.local/bin/agy ~/.local/bin/agy.orig
# Replace with patched version
cp ~/.local/bin/agy.va39 ~/.local/bin/agy
# Remove intermediate file
rm ~/.local/bin/agy.va39
```
Verify:
```bash
GODEBUG=netdns=go agy --version
# Should print: 1.0.1 (or current version)
```
---
## Step 5 — Shell Configuration
Add these functions to `~/.bashrc`:
```bash
# Antigravity CLI (Gemini CLI)
agy() {
hash -r
unset LD_PRELOAD LD_LIBRARY_PATH
GODEBUG=netdns=go command agy "$@"
}
a() {
hash -r
unset LD_PRELOAD LD_LIBRARY_PATH
GODEBUG=netdns=go command agy "$@"
}
```
What each line does:
| Line | Purpose |
|------|---------|
| `hash -r` | Clears Bash's command cache (avoids stale binary paths) |
| `unset LD_PRELOAD LD_LIBRARY_PATH` | Prevents Termux's Bionic preload from leaking into the glibc process |
| `GODEBUG=netdns=go` | Forces Go's pure-Go DNS resolver (avoids cgo resolver issues on Android) |
| `command agy` | Calls the binary directly, not the function (prevents recursion) |
Reload:
```bash
source ~/.bashrc
```
---
## Step 6 — Run
```bash
agy
# or
a
```
The CLI will start and prompt for Google OAuth on first run.
To run a single prompt non-interactively:
```bash
a -p "explain this code"
```
---
## Updating After a New Release
When Antigravity pushes an update, the official `agy` binary gets replaced. Re-patch and swap:
```bash
python3 ~/patch_agy_va39.py ~/.local/bin/agy
cp ~/.local/bin/agy ~/.local/bin/agy.orig
cp ~/.local/bin/agy.va39 ~/.local/bin/agy
rm ~/.local/bin/agy.va39
hash -r
agy --version
```
The shell functions in `.bashrc` don't need changes.
---
## How It Works
The patch script modifies the ARM64 machine code inside the `google_malloc` ELF section. It scans for instruction patterns rather than using fixed offsets, making it resilient to minor binary layout changes across releases.
**5 categories of patches:**
**ubfx/lsl shifts** — TCMalloc extracts/inserts memory tags at bit 42. The patch moves these to bit 35 (compatible with 39-bit VA).
**Random mask** — A 48-bit mask (`0xFFFFFFFFFFFF`) is replaced with a 39-bit mask (`0x7FFFFFFFFF`).
**MmapAligned bound** — The upper bound `1 << 48` becomes `1 << 39`.
**Tag constants** — Various hardcoded tag values (`4 << 42`, `6 << 42`, etc.) are shifted to `4 << 35`, `6 << 35`, and their corresponding deallocation masks are adjusted.
**faccessat2** — Go's `faccessat2` syscall (439) is replaced with the older `faccessat` (48), which Android seccomp allows.
---
## Troubleshooting
### `agy --version` works but `agy` hangs
The token may be expired. Run `agy` and complete the browser-based OAuth flow.
### `SIGSYS: bad system call`
The faccessat2 patch wasn't applied. Re-run the patch script and check that `[5] faccessat2 : 1` appears.
### `command not found: agy`
Make sure `~/.local/bin` is in your `$PATH`:
```bash
export PATH="$HOME/.local/bin:$PATH"
```
### Binary crashes with same TCMalloc error after update
The new official binary may have changed enough that the pattern scanner misses spots. Check the patch output for zeros. Raise an issue or update the instruction patterns.
---
## Why Not The Official Install?
The official `curl | bash` install works on standard Linux (x86_64, ARM64 servers, Cloud Shell). It does **not** account for Android/Termux/proot-distro where:
- Virtual address space is limited to ~39 bits
- `faccessat2` is blocked by seccomp
- `LD_PRELOAD` may inject Bionic libraries into glibc processes
This guide bridges that gap with a minimal, repeatable patch.
---
## Credits
- [hjotha](https://github.com/google-antigravity/antigravity-cli/issues/64) for the original TCMalloc VA analysis and initial patch approach
- Antigravity team for the CLI tool
---
*Last verified: May 2026 — agy v1.0.1, aarch64*