◉ANTIGRAVITY LABJP
Articles/Integrations
⬡ Integrations/2026-06-13Advanced

Running the Antigravity CLI (agy) Headless in CI: Working Around the Non-TTY stdout Problem

Run agy -p inside GitHub Actions or cron and the output you saw locally can vanish, while the exit code still returns 0. Here is how non-TTY detection causes it, plus a robust setup using a pseudo-TTY, defensive text parsing, and API-key auth so you always capture the result.

antigravity-cli16agy8ci-cd16github-actions9automation95

✦ Premium Article

One morning I opened the GitHub Actions log and froze. The step that called agy -p "..." showed a green check — "success" — yet not a single line of output was there. Running the exact same command in my local terminal, the agent replied at length. In CI, the log held only blank space, and the exit code was 0. "Succeeded, but did nothing": the hardest failure mode to notice.

As an indie developer I run a few sites on unattended update pipelines, calling agents from the terminal as an extension of my personal tooling. Wiring the Antigravity CLI (the agy command) into that, I lost half a day to this trap. The cause was not a bug in my script — it was that agy changes its behavior depending on whether a human is watching the terminal.

This behavior is barely mentioned in the official tutorials, but it is fatal for unattended runs. Let me walk through how it reproduces and the robust setup I eventually settled on.

Why agy's output disappears in CI

An interactive agent CLI like agy checks at startup whether standard output is connected to a TTY (a terminal). When a person is interacting (a TTY), it streams a colored response and draws spinners and progress. When output is redirected to a pipe, a file, or a subprocess — a "non-TTY" — the CLI disables that terminal-oriented rendering.

The problem is that in the current version, agy --print / -p can drop the final response from standard output when run under a non-TTY. This has been reported as an issue in the Antigravity CLI (more below), and it stems from terminal rendering and "machine-readable stdout" not yet being cleanly separated. A GitHub Actions step, cron, subprocess.run(), a $(agy -p ...) command substitution — all of these are non-TTY, so they all hit the same symptom.

In other words, "works locally but goes empty in CI" is triggered not by environment variables, the network, or permissions, but by standard output not being connected to a terminal. Whether you can suspect this first dramatically changes your debugging time. For the basics of launching agy and reading its slash commands, see Getting hands-on with the Antigravity CLI: migration steps and slash commands.

A minimal repro: one pipe is enough to empty it

You don't need a server to reproduce this locally. Run the following in order and the symptom shows up directly.

# (1) Direct run — connected to the terminal, so you see the response
agy -p "Summarize the README in this directory in three lines"
 
# (2) Add a single pipe — now non-TTY, and the output can come back empty
agy -p "Summarize the README in this directory in three lines" | cat
 
# (3) Command substitution behaves the same — OUT is empty, exit code is 0
OUT="$(agy -p "Summarize the README in this directory in three lines")"
echo "captured length = ${#OUT}"   # -> can be 0
echo "exit code = $?"              # -> 0 (treated as success)

In (1) the response is visible, but in (2) and (3) it can be empty. Worse, $? returns 0, so a naive script concludes "success" and moves on. That is exactly "succeeded but did nothing." A CI step is, in essence, in the same situation as (2) and (3).

Note: this non-TTY stdout drop is tracked as an issue in the Antigravity CLI repository (agy --print / -p silently drops stdout when run with a non-TTY). It may improve or change between versions, so always confirm the behavior with your own agy --version.

✦

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 now diagnose why agy -p output silently disappears in CI or cron (non-TTY detection) and reliably capture it by attaching a pseudo-TTY
✦You'll learn a parsing pattern that does not depend on the still-unstable --output-format json, so your pipeline keeps working on plain text output
✦You'll be able to wire agy into unattended pipelines safely, accounting for API-key auth, exit codes, and idempotency
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 $10 for lifetime access
View Membership →

Related Articles

✦ Tips2026-08-21
The pipe in my wrapper was swallowing Antigravity CLI's exit code
CLI 1.1.14 surfaces language server failures as a non-zero exit. That improvement never reaches you if your automation wrapper drops the exit code first. I ran sixteen calling patterns on bash 5.1 and recorded which ones make the failure disappear, plus the fixes that bring it back.
⬡ Integrations2026-09-12
Antigravity CLI 1.1.28 turns a print-timeout overrun into a successful exit — rechecking unattended runs
Since 1.1.28, an expired --print-timeout returns partial output and exits successfully instead of failing. Fetching an external URL now asks for approval first. Here is how I rebuilt an unattended pipeline that judged everything by the exit code.
⬡ Integrations2026-08-22
Driving Antigravity CLI with stream-json Input: Where to Split the Conversation
The print mode now accepts --input-format stream-json, letting an external driver hold one session open and feed it jobs one at a time. I measured how much a single conversation carries across 24 jobs, found that a byte budget splits work in the wrong place, and worked out how to choose the split boundary instead.