ANTIGRAVITY LABJP
Articles/Agents & Manager
Agents & Manager/2026-09-09Intermediate

When an Antigravity Scheduled Task Never Runs: Check sidecar.json, Then enabled, Then the Logs

A checking order for Antigravity 2.0 Scheduled Tasks that quietly do nothing: where sidecar.json lives, how the directory name becomes the ID, the enabled flag in config.json, and the logs under sidecar_data.

Antigravity368Scheduled Taskssidecarcron3automation92

A job that was supposed to run before dawn had left nothing behind by the time I looked the next morning. No error notification, no partial output — just a directory holding exactly what it held the day before. I've had that kind of morning more than once.

Antigravity 2.0 Scheduled Tasks let you write a cron expression and have a command or an agent call start on its own at the appointed time. From chat, a single line is enough: /schedule "0 9 * * 1-5" Summarize pending PR reviews for the week. Writing it is almost anticlimactically easy. What's hard is the part nobody advertises — when an unattended job goes wrong, there is no one sitting in front of the screen.

The thing I'd say first is that the order of checking matters. Before you start doubting the contents of your config, establish whether the process started at all. Skip that, and you'll end up rewriting a configuration that was correct the whole time.

"It didn't run" is actually two different states

The first state is that the process never started. The second is that it started, did nothing useful, and exited quietly. From the outside these look identical, but the causes live in completely different places.

The first one is almost always configuration: where the file sits, what the ID is, and whether it's enabled. The second one is your script — permissions, working directory, or an environment variable that isn't what you assumed.

For a while I didn't separate the two, and I kept editing config files. That went about as badly as you'd expect. The task had been starting reliably every single time; my script was dying on its first line.

The directory name becomes the sidecar ID

Scheduled Tasks are built on sidecars — background processes whose lifecycle Antigravity manages, launching them and restarting them when they crash. They're discovered by searching for sidecar.json, and there are two places it can live.

  • Global: ~/.gemini/config/sidecars/<name>/sidecar.json
  • Plugin: ~/.gemini/config/plugins/<pluginName>/sidecars/<name>/sidecar.json

The directory name is the sidecar's ID. Plugin sidecars get the ID <pluginName>/<sidecarName>. Hold on to that, because the enabling step below matches on it.

The other easy thing to miss: the sidecar's own directory is the working directory for its command. That's convenient — you can point at a script by relative path — but if you also wrote a relative path to some repository elsewhere, the job will not find it once it runs.

KeyTypeWhat it does
commandstringExecutable to run. Mutually exclusive with builtin
builtinstringBuilt-in function; currently schedule
argsstring[]Arguments. With schedule, the first is a cron expression
restart_policystringalways / on-failure / never (defaults to always)
envobjectEnvironment variables for the process
display_namestringName shown in the UI — not the ID

Here's a sidecar that runs a local script at 5:00 every morning:

{
  "display_name": "Morning asset sync",
  "description": "Pulls in new artwork for my wallpaper app before the day starts",
  "builtin": "schedule",
  "args": ["0 5 * * *", "/bin/bash", "run.sh"],
  "restart_policy": "always"
}

With builtin: "schedule", the first element of args is a standard five-field cron expression and the rest is the command plus its arguments. command and builtin are mutually exclusive, so you can't set both.

restart_policy defaults to always. For a scheduler you want it resident, so the default is fine. But when I register a plain command sidecar that only needs to do its thing once, I set never or on-failure deliberately rather than letting the default decide for me.

Nothing starts until you write enabled

This was the wall I hit first. Sidecars are disabled unless you explicitly enable them. That happens in ~/.gemini/config/config.json:

{
  "sidecars": {
    "morning-asset-sync": { "enabled": true },
    "my-plugin/review-triage": { "enabled": true, "projectId": "YOUR_PROJECT_ID" }
  }
}

The key is the ID — that is, the directory name. I burned half a day here because I'd typed the display_name instead. The display name exists for the UI; it plays no part in this lookup.

projectId is required when the sidecar creates agent conversations through agentapi new-conversation. If your job only shells out to a command, you can leave it out.

Logs and events land under sidecar_data

There's exactly one place to look to answer "did it start?". Under ~/.gemini/antigravity/sidecar_data/<sidecarId>/ you'll find three directories:

  • logs/ — timestamped captures of stdout and stderr
  • events/ — JSON records of agentapi calls
  • data/ — persistent storage, reachable via ANTIGRAVITY_EXECUTABLE_DATA_DIR

That's where the triage ends. If there's a file in logs/ for today, the process started and your problem is inside the script. If there's no file at all, go back to enabled and the ID.

ls -lt ~/.gemini/antigravity/sidecar_data/morning-asset-sync/logs/ | head -5

Write the proof at the start of the run, not at the end

Here's the mistake I kept making. My scripts only logged when they succeeded. So a day where the job ran and produced nothing looked exactly like a day where it never started, and morning-me couldn't tell them apart.

Now every run leaves exactly one line saying success, skip, or failure. The proof that a job ran gets written when it starts, not when it succeeds. That's the one line I hold to even on days when I'm rushing.

#!/bin/bash
set -uo pipefail
LOG_DIR="${ANTIGRAVITY_EXECUTABLE_DATA_DIR:-$HOME/.local/share/agy-jobs}"
mkdir -p "$LOG_DIR"
STAMP="$(date +%Y-%m-%dT%H:%M:%S%z)"
LOG="$LOG_DIR/$(date +%Y-%m-%d).log"
 
echo "$STAMP START asset-sync" >> "$LOG"
 
if [ ! -d "$HOME/material/incoming" ]; then
  echo "$STAMP SKIP  no incoming directory, nothing to do" >> "$LOG"
  exit 0
fi
 
if python3 sync_assets.py >> "$LOG" 2>&1; then
  echo "$STAMP OK    asset-sync" >> "$LOG"
else
  echo "$STAMP FAIL  exit=$? asset-sync" >> "$LOG"
  exit 1
fi

The payoff shows up when you read it back. A day with a START but no OK and no FAIL means the run was cut short or kept restarting. A day with no START at all means it never launched. Three states, and tomorrow morning you know which file to open.

One more caution, from my own measurements rather than the docs. A cron expression with comma-separated hours — something like 30 4,16 * * * — has fired at only one of those times in my setup. That was a different scheduler, not Antigravity, so treat it as a caveat rather than a rule. Still, since I started splitting "twice a day" into two separate task definitions, I've stopped losing runs. It's worth measuring once before you trust a comma.

One step for tomorrow

Create a single sidecar that does nothing but write date every minute. Put ["* * * * *", "/bin/bash", "-c", "date >> heartbeat.log"] in args, enable it in config.json, and watch the files accumulate in logs/. Once that path is proven, swapping in real work is the easy part.

As unattended jobs become part of how you actually operate, the next question is how to detect the runs that never happened — reconciling an execution ledger against a table of what you expected. I wrote that side of it up in With scheduled agents, I now look for runs that never happened before I look for failures.

Thank you for reading this far. I hope your overnight jobs leave footprints you can find in the morning.

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 →

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

Agents & Manager2026-09-06
With scheduled agents, I now look for runs that never happened before I look for failures
My run ledger showed a 100% success rate while the evening slot had not fired for two weeks. Here is the reconciliation I now run against an expected-fire table, with measured notes on cron expansion, exit codes, and how the ledger itself gets written.
Agents & Manager2026-07-15
When Your Agent Commits a .bak File: Why Fix-Tool Artifacts End Up in Git
Backup files like .bak and .orig slip into commits after an agent runs a --fix tool. Here are the reproduction conditions, the real root cause, and three fixes: narrowing the staged scope, wrapping the fixer, and adding a pre-commit extension gate.
Agents & Manager2026-07-12
What to Delegate to an Antigravity Agent and What to Keep by Hand, After Two Weeks
After two weeks of handing my daily solo-dev tasks to Antigravity agents, a clear line emerged between the work I was glad to delegate and the work I had to pull back. A retrospective with the operational log.
📚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