ANTIGRAVITY LABJP
Articles/AI Tools
AI Tools/2026-08-28Intermediate

Three things I had to fix before my status line could tell me what a session cost

Getting real session cost into a custom Antigravity CLI status line took three fixes: how the script reads stdin, how to tell whether your build sends a cost field, and how to keep a usage ledger that does not double-count.

antigravity447antigravity-cli13statuslinecost4indie-development5

Premium Article

I could read the invoice at the end of the month. What I could not do was answer a simpler question: which piece of work cost the most last week?

As an indie developer moving between several repositories in a single day, almost everything I run is short. A few large design requests, and then hundreds of completions, searches, and quick confirmations. Those short sessions are exactly the ones that never show up in a monthly total in any useful shape.

CLI 1.1.21 added an unrounded session cost to the status line data model. I sat down to wire that into a custom status line, and hit three separate problems before anything useful appeared on screen. Here they are, in the order I met them.

Start by dumping what your own build actually sends

The mechanism is simple. Put a statusLine block in ~/.gemini/antigravity-cli/settings.json, and whenever the agent state changes, the CLI runs your command, pipes a JSON state payload into its stdin, reads a string from stdout, and renders it at the bottom of the prompt panel.

{
  "statusLine": {
    "type": "command",
    "command": "~/.gemini/antigravity-cli/statusline.sh"
  }
}

The official status line documentation lists the fields in that payload. As I will get to below, that table is not guaranteed to match the build on your machine. So the first thing worth doing is not reading the table — it is writing the payload to a file, once.

#!/usr/bin/env bash
# ~/.gemini/antigravity-cli/statusline.sh  (throwaway, for inspection only)
# Goal: capture exactly one real payload from the build you are running.
DUMP="$HOME/.gemini/antigravity-cli/payload-sample.json"
 
payload="$(timeout 2 cat)"
 
# Only write once. If it is overwritten on every state change,
# you lose the ability to diff it against a later version.
if [ -n "$payload" ] && [ ! -s "$DUMP" ]; then
  printf '%s\n' "$payload" > "$DUMP"
fi
 
echo "sampling..."

Start the CLI once, open payload-sample.json, and you know where you stand. I skipped this step, went straight from the docs table to writing code, and took the detour described below.

The reason for timeout 2 cat is the next section.

Reading stdin the obvious way can leave your script hanging

The first line I wrote was this:

payload="$(cat)"

In an ordinary shell script that is fine. A status line script is not ordinary: it runs under a wall-clock budget and has to print something and exit inside it. And during auth token refresh or conversation resume, the CLI can hold the stdin pipe open without writing to it and without closing it. cat waits for end-of-input, so in that state it simply does not come back.

I reproduced the condition locally — a parent that holds the pipe and never writes — and measured both spellings from inside the script itself.

How stdin is readParent writes and closesParent holds the pipe openExit code
payload="$(cat)"read in 4 msnever returns; killed from outside124
payload="$(timeout 2 cat)"read in 4 msreturns empty after 2,004 ms0

Exit code 124 means it was killed for running out of time. On screen this shows up as vague symptoms — the status line stops updating, input feels sticky for a moment — which is why it takes a while to suspect the way your script reads its input.

The fix is to guarantee the return, and to have somewhere to fall back to when nothing arrives.

#!/usr/bin/env bash
# A status line that always returns inside its budget
CACHE="$HOME/.gemini/antigravity-cli/.statusline-last"
 
payload="$(timeout 2 cat)"
 
if [ -z "$payload" ]; then
  # Nothing arrived. Print the previous line and exit quietly.
  # Exiting non-zero here just puts an error on screen every few seconds.
  cat "$CACHE" 2>/dev/null || echo "agy"
  exit 0
fi
 
line="$(printf '%s' "$payload" | python3 -c '
import sys, json
p = json.load(sys.stdin)
cw = p.get("context_window") or {}
vcs = p.get("vcs") or {}
print("{} | {} | ctx {:.0f}%".format(
    (p.get("model") or {}).get("display_name", "?"),
    vcs.get("branch", "-"),
    cw.get("used_percentage") or 0,
))
')"
 
printf '%s' "$line" | tee "$CACHE"

I also tried read -r -t 2, which bounds the wait but only captures the first line — and the payload can arrive pretty-printed across several lines. timeout 2 cat handles both.

One more thing that cost me time. If you test with something like ( sleep 20 ) | timeout 8 ./statusline.sh, the script returns in two seconds but your shell waits for the left side of the pipeline, so it feels like twenty. It is easy to conclude your script is slow when your test harness is. Measure from inside the script.

# Time it from inside; an outer `time` measures the whole pipeline
S=$(date +%s%N)
payload="$(timeout 2 cat)"
E=$(date +%s%N)
echo "took=$(( (E - S) / 1000000 ))ms" >&2

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
You can write a status line script that always returns inside its time budget, instead of one that quietly wedges the CLI
You can confirm what your own build actually sends, rather than trusting a field table that may lag behind the release notes
You can keep a usage ledger that does not double-count, and answer which project your spend is concentrated in
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

Related Articles

AI Tools2026-06-17
Your Antigravity LLM App Drifts on Cost and Quality While the Dashboard Stays Green — Instrumentation Field Notes
Watching only total cost and latency hides the slow drifts that hurt. These are field notes on attributing telemetry by feature, tenant, and prompt version so you catch quality regressions and cost spikes early.
AI Tools2026-08-19
When Antigravity Swaps Its Default Model, Only the Jobs You Narrowed First Survive
Gemini 3.7 Flash is now the default model for Antigravity agents. The places you never configured are exactly the places that shift silently. Here is how to inventory your default-model exposure, then split your jobs into move-now and hold, scored by how much output freedom you left open.
AI Tools2026-06-18
Using the v2.1.4 Quota Screen for a Weekly Reckoning: Reading Used and Remaining to Run an Indie Budget
How to turn the used/remaining display in the reworked Antigravity v2.1.4 quota screen into a weekly reckoning instead of a gut feeling. Baseline recording, burn-rate math, and allocation across multiple projects, written as an indie-dev operating routine.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →