One morning I opened the usual scheduled-run log and found the job marked "success" — yet none of the output it was supposed to produce existed anywhere. Scrolling back, a single 401 Unauthorized appeared partway through, and after that the output simply stopped.
Here is the conclusion up front. The painful part is not the 401 itself. After the 401, the Antigravity CLI dropped into an interactive re-login prompt, and in an unattended run with no terminal, nobody was there to answer it — so the job hung until the parent timeout folded it up. The exit status looked like success; the contents were empty. Running automation for the Dolice Labs sites as an indie developer, this "silent stall" is the failure mode that cost me the most time.
On June 18, the Gemini CLI ends its free and Pro/Ultra request handling and consolidates into the Antigravity CLI. Any automation built on the old gemini-derived credentials has to be rebuilt around that date — which makes right now the moment to redesign how your unattended jobs authenticate.
A job marked "success" that did nothing
The first trap is that the symptom is recorded as success. From the scheduler's point of view, the process returned and exited. In reality the agent took a 401 during the auth step before the real work began, fell into a prompt asking it to log in again, and sat waiting for input until the parent timeout terminated it.
The first move when isolating this is to verify that auth is alive, separately from the real task. If you mix the production task and the auth check together, you cannot tell which one failed.
#!/usr/bin/env bash
set -euo pipefail
# Before the real work, check only authentication.
# If it is dead, stop explicitly — before falling into a prompt.
if ! antigravity auth status >/dev/null 2>&1; then
echo "[preflight] authentication is invalid; aborting job." >&2
exit 78 # EX_CONFIG: fail explicitly as a configuration problem
fi
echo "[preflight] auth OK"Subcommand names can change between versions. Run antigravity --help once on your installed build and swap in whatever lightweight command reports auth state. The point is to decide whether auth is alive or dead without entering a prompt, before the real work starts.
Why it ran fine until yesterday, then stopped
The cause almost always reduces to two things.
First, tokens obtained through an interactive login have an expiry. If you sign in once at your desk and then reuse that token for scheduled runs, it expires after days to weeks, and one morning you suddenly get a 401. Because it worked for a stretch, the cause is hard to pin down.
Second, the auth foundation gets rebuilt. When the underlying tool is replaced by a different implementation — as in the June 18 migration — credentials that were valid can be invalidated. The config file looks identical to before, yet the CLI no longer accepts it. That asymmetry is the tell.
In both cases nothing is "misconfigured" — the token simply expired, or the foundation changed. Re-reading the JSON will not fix it.
Move to authentication that does not rely on interactive login
For unattended runs, lean on a key you can pass through an environment variable rather than an OAuth flow that needs a human. With a key, the interactive prompt never appears in the first place, so even if you hit a 401 you cannot get stuck "waiting to re-login."
# From cron / CI, pass a service key through an environment variable.
# Never hard-code the key value into the script — inject it from a secret store.
export ANTIGRAVITY_API_KEY="YOUR_ANTIGRAVITY_API_KEY"
antigravity run --non-interactive --input task.mdKeep the real key value out of the repository and out of the logs. The urge to echo it while debugging is real, but always mask it here. If you stream an environment variable through a script running under set -x, it lands in your trace, so wrap the key-handling section in set +x.
set +x # stop tracing from here
export ANTIGRAVITY_API_KEY="$(cat /run/secrets/antigravity_key)"
antigravity run --non-interactive --input task.md
set -xHold the timeout and the "prompt fall" on the job side
Even after moving to a key, holding a timeout and standard input on the job side is a useful insurance: if some future reason drops you into a prompt, it still prevents a silent stall. Tying standard input to /dev/null means that if a prompt does appear, it hits EOF immediately and exits, instead of waiting forever.
run_with_guard() {
local timeout_sec="$1"; shift
# Tie stdin to /dev/null so any prompt ends at EOF the moment it appears.
if ! timeout --signal=TERM "${timeout_sec}" "$@" </dev/null; then
local code=$?
echo "[guard] abnormal exit=${code} (124 means timeout)" >&2
return "${code}"
fi
}
run_with_guard 600 antigravity run --non-interactive --input task.mdIf timeout returns 124, treat it not as "the work was heavy" but as a signal that "something started waiting." Do not swallow it as success — record it explicitly as a failure. That distinction is what keeps the silent stall from ever happening again.
Smoke-test auth every morning, before the real job
The last safeguard is to confirm only that auth is alive with a one-minute task before the heavy production job. Instead of discovering an expiry at production time, you catch it ahead of time.
# Place this ahead of the production job. A reply means auth is alive.
if antigravity run --non-interactive --input - </dev/null <<'PROMPT' >/dev/null 2>&1
Reply with just: ok
PROMPT
then
echo "[smoke] auth OK; proceeding to the production job."
else
echo "[smoke] auth failed; the key needs rotating." >&2
exit 78
fiWhen this smoke test fails, I do not proceed to production — I send myself a notification. Rotating a key is a few minutes of work, but if you only notice it "when you see an empty log the next morning," you have lost a full day of output. A small check up front protects that day.
Your next step
Open one of the Antigravity-related jobs you currently have on a schedule and check whether its auth comes from an interactive login or from a key. If it is still interactive, move it to an environment-variable key first, then add one line of preflight auth check. Those two changes alone go a long way toward preventing that quiet morning stall — and I hope they keep your morning logs from coming up empty.