◉ANTIGRAVITY LABJP
Articles/AI Tools
⚙ AI Tools/2026-10-18Intermediate

The Terminal Stayed on Running Forever: Three Ways the Finish Signal Never Arrives, Checked Before Raising the Timeout

When a command you handed to an agent sits on Running, it may not be slow at all; the finish signal may simply never arrive. I measured three shapes in a plain shell: waiting on stdin, a child process holding the output pipe, and waiting on a prompt. One run was cut off at 3.0 seconds, another held for 30.0, and I note where the fixes break.

terminal9troubleshooting117stdintimeout7indie dev22Antigravity388

I had handed an image-cleanup command to the agent and stepped away. When I came back, the terminal panel still said Running, and the output hadn't grown by a single line.

I assumed it was just slow and waited about ten minutes. When that didn't help, I doubled the time limit I'd given the agent. It didn't help either: the command simply took longer to not finish.

Looking back, I had been treating "slow" and "the finish signal never arrived" as the same thing. Below I split the second one into three shapes and give the numbers I measured for each.

What I'd say first: change what you suspect

When a command stays on Running, suspect not how heavy the work is but whether the command has any way left to say it's done. In my case the blocked paths came down to three.

ShapeWhat is happeningWhat gives it away
Waiting on stdinEOF never arrives, so the command keeps readingA bare cat, or any read with no file given
A lingering childThe parent exited, but a child still holds the output pipeA trailing &, starting a dev server or a watcher
Waiting on a prompt or editorIt stopped at a screen meant for a persongit commit without -m, installers without -y

One caveat: these numbers come from a plain Linux shell (Python 3.13, git 2.43), not from Antigravity's terminal itself. I can't promise the agent's terminal behaves identically, so I'd rather you confirm in your own setup.

Shape 1: waiting on stdin

A command that reads input won't finish until the input closes. I gave it an input that stays open and cut it off at 3 seconds.

# Make an input that never reaches EOF
timeout 3 sh -c 'cat' < <(sleep 100)
echo "exit=$?"      # -> 124 (cut off at 3.0 s: it would not end by itself)
 
# Close the input up front
timeout 3 sh -c 'cat' < /dev/null
echo "exit=$?"      # -> 0 (finished in 0.007 s)

python3 -c 'import sys; print(len(sys.stdin.read()))' behaved the same: cut off at 3.0 seconds on an open input, and an immediate 0 with /dev/null.

Here is why I write it this way. < /dev/null tells the command up front that no input is coming, so the reader gets EOF and can end on its own.

Shape 2: a lingering child

This is the one I fell into first. The parent shell is already done, yet whatever is collecting the output keeps waiting.

# Leave a sleep in the background and collect output with cat
timeout 40 sh -c 'sleep 30 & echo started' | cat
# -> "started" appears instantly, but the whole command returns after 30.0 s
 
# Detach the child's output and input
timeout 40 sh -c 'sleep 30 >/dev/null 2>&1 </dev/null & echo started' | cat
# -> returns in 0.004 s

Same &, yet 30.0 seconds versus 0.004. The only difference is whether the child keeps holding the parent's output pipe. If you ask an agent to "start the dev server and move on," this is exactly what you get.

For start-only commands, I always push the output to a file.

# Start the server in the background and let the terminal move on
nohup npm run dev > dev.log 2>&1 < /dev/null &
echo "pid=$!"

Shape 3: waiting on a prompt or editor

The third stops at a screen that expects a human. git commit without -m opens an editor and waits. When I forced GIT_EDITOR=true, it didn't wait; it printed Aborting commit due to empty commit message. and returned exit code 1. Instead of hanging, it became a visible failure.

So the fix here isn't to wait out the stopped screen. It's to write the command so the screen never appears.

  • Always pass -m to commits.
  • Pass -y or --yes to installers.
  • If you can't avoid a dialog, don't hand that step to the agent; run it yourself.

Telling the shapes apart

You can tell which of the three it is by peeking from another terminal while the command is stuck. On Linux:

# Find the stuck command's PID, then look at its state and its input
ps -o pid,stat,wchan:14,cmd -p <PID>
ls -l /proc/<PID>/fd/0

For the cat above, wchan read anon_pipe_read and stdin was pipe:[2107], meaning it was still reading from a pipe. With /dev/null it had already finished before I could look. (On macOS, lsof -p <PID> gets you close.)

For a lingering child, ps -o pid,ppid,stat,cmd -C sleep showed the sleep 30 with its parent reassigned to 1. The parent was gone and only the child was alive, which you can confirm by eye.

Where it breaks, and the line I draw

These fixes have limits.

< /dev/null breaks commands whose whole purpose is to read input, such as psql < query.sql or docker run -i. And exit code 124 from timeout means "timed out," not "failed," so you can't tell how far the work got. A cutoff narrows down the cause; it doesn't solve anything.

Looking back, doubling the wait first may have been my way of avoiding the effort of looking at the cause. These days I follow this order: before extending the wait, I build a path for the finish signal to arrive. I extend only after confirming the command really is slow.

I put these three lines in the instructions I give the agent.

- For commands that read input, add < /dev/null and wrap them in a 60-second timeout
- Detach background processes with nohup and redirect their output to a file
- Use -m for git commit and -y for installers, so no interactive screen appears

One next step

If a command is stuck on Running right now, run timeout 20 <the same command> < /dev/null first. If it returns within 20 seconds it was waiting on input; if not, suspect a child process or a prompt next. For the other way work stalls, see Antigravity Keeps Asking Permission for the Same Command? Fix the Approval Dialog Loop, and for telling a timeout apart from a success, see Antigravity CLI 1.1.28 turns a print-timeout overrun into a successful exit — rechecking unattended runs.

As an indie developer who hands routine commands to an agent, this is a check I try not to skip, even on a rushed day.

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 →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⚙ AI Tools2026-10-06
A report of a cache cleanup that wiped a drive: why I now route every delete through a checkpoint
After reading about a cache cleanup request that ended with a developer's drive contents gone, I reopened my own cleanup script. Here is a small Python checkpoint that narrows how far a delete can reach, plus where I draw the line on what to hand to an agent without approval.
⚙ AI Tools2026-09-11
Starting with AGY_CLI_HIDE_LOGO: tuning Antigravity CLI for narrow terminals, screen readers, and recordings
Logo art, your email address, output that swallows the pane, a copy you never asked for. Here are the Antigravity CLI environment variables and settings that quiet the display, grouped by the three situations where they matter.
⚙ AI Tools2026-08-14
When a Fast Model Feels Slow, Look at Reasoning Effort Before Switching Models
Antigravity lets you pick a reasoning effort level per model. Here is how I decide between Low, Medium, and High based on the shape of the task, what to check when changing it makes no difference, and a small script for correcting your own judgment with records instead of memory.