One evening I asked an Antigravity agent to take a Next.js project all the way to a production build, and it stopped on the very last step with Missing environment variable: STRIPE_SECRET_KEY. In my own terminal, npm run build ran cleanly, and echo $STRIPE_SECRET_KEY printed the expected value — yet the moment the agent ran the same command, the variable was empty. If you have spent an afternoon staring at the same error, you are not alone.
In almost every case I have seen, this is not a .env configuration problem in the strict sense. It is a misunderstanding of how Antigravity agents propagate environment variables to the processes they spawn. Below I walk through the three paths I have personally tripped over, and finish with a four-step setup I now use on every personal project.
Picture the three paths an env var can travel before reaching your agent
Before you start changing files, it helps to keep three propagation paths in your head. Diagnosis becomes much faster once you know which one is broken.
- Path A — Inheritance from the shell. When you launch Antigravity from a terminal (
antigravity .), every variable that your shell startup exported is inherited by child processes - Path B — Framework-level dotenv loading. Frameworks like Next.js (
.env.local), Vite (import.meta.env), or Python'spython-dotenvactively read files at runtime - Path C — The env block the agent constructs. When Antigravity spawns a subprocess (typically because of a task definition or
AGENTS.md), it builds an explicit env block that may or may not include what you expect
Whenever I hear "it works in the terminal but not from the agent," one of these paths is silently severed. Let's walk through each.
Case 1: GUI-launched Antigravity does not inherit your shell config
If you start Antigravity from the macOS Dock, the Windows Start Menu, or a Linux app launcher, the variables you export in ~/.zshrc or ~/.bashrc will not be present for the agent. This was the first trap I fell into, and I lost half a day to it.
The check is straightforward. Open Antigravity's integrated terminal and run:
# Quick check that your shell init actually ran
echo "shell=$SHELL"
echo "node=$(which node)"
echo "stripe=${STRIPE_SECRET_KEY:0:7}..." # show just the first 7 characters
# Expected output when shell init was inherited correctly:
# shell=/bin/zsh
# node=/Users/you/.nvm/versions/node/v22.5.0/bin/node
# stripe=sk_test...If stripe=... is empty, or node resolves to the system default (/usr/bin/node), your interactive shell config is not running. GUI-launched processes only inherit values from ~/.zshenv (zsh) or launchctl setenv (macOS) — anything you put in ~/.zshrc is invisible to the agent.
My personal rule, after living with this issue for a couple of years, is to keep secrets inside the project as .env.local rather than in the shell. Shell-only setups always break the moment you move to a new machine.
Case 2: Values you wrote in AGENTS.md are guidance, not an env block
As I covered in the AGENTS.md not-working diagnosis guide, AGENTS.md is a natural-language brief for the agent. It is not a process env block.
# Bad — the agent will read this, but never export anything
## Environment
- API_KEY = sk_live_xxx
- DATABASE_URL = postgres://...Even when the agent dutifully reads this file, the values do not show up as process.env.API_KEY for any process it spawns. Writing real secret values in markdown is also a leakage risk. Instead, use AGENTS.md to describe where the secrets live and how they are loaded.
# Better
## Environment
- All secrets live in `.env.local` at the repo root
- Update `.env.example` whenever you add a new variable
- Local commands rely on dotenv being auto-loaded by `npm run dev`The agent's job is to plan and execute. Tell it where the values come from and how to load them; do not bake them into the brief.
Case 3: Your framework's dotenv loader is looking at the wrong working directory
Next.js and Vite resolve .env.local relative to the current working directory of the spawned process. If your agent thinks it ran npm run build from the monorepo root but actually cd'd into a sub-package first, there will be no .env.local to find, and nothing will be loaded.
// scripts/check-env.mjs — make the env-resolution path observable
import { existsSync } from 'node:fs';
import path from 'node:path';
import dotenv from 'dotenv';
const cwd = process.cwd();
console.log('CWD:', cwd);
// Walk through the candidate locations
const candidates = [
path.join(cwd, '.env.local'),
path.join(cwd, '.env'),
path.join(cwd, '..', '.env.local'),
];
for (const file of candidates) {
console.log(file, existsSync(file) ? 'FOUND' : 'missing');
}
// Load explicitly
const result = dotenv.config({ path: '.env.local' });
console.log('Loaded keys:', Object.keys(result.parsed ?? {}));
console.log('STRIPE prefix:', (process.env.STRIPE_SECRET_KEY ?? '').slice(0, 7));
// Expected output when run from a monorepo root:
// CWD: /Users/you/projects/myapp
// /Users/you/projects/myapp/.env.local FOUND
// Loaded keys: [ 'STRIPE_SECRET_KEY', 'DATABASE_URL', 'OPENAI_API_KEY' ]
// STRIPE prefix: sk_testWhen I ask the agent to run this script, the log immediately tells me where it looked and what it found. That observability turns a 30-minute hunt into a 30-second one. I now drop this script into every new project before I write a single feature.
A four-step setup that propagates env vars reliably
Here is the actual configuration I run on personal projects. The example uses Next.js, but the same idea applies to Python (python-dotenv) or Go (godotenv).
Step 1 — Consolidate everything into .env.local at the repo root.
Avoid scattering many .env.* files. Keep one .env.local for local development and a checked-in .env.example describing the shape.
Step 2 — Make dotenv loading explicit in package.json.
{
"scripts": {
"dev": "next dev",
"build": "next build",
"build:agent": "node -r dotenv/config node_modules/.bin/next build dotenv_config_path=.env.local",
"test:env": "node scripts/check-env.mjs"
}
}A separate build:agent script gives you a stable entry point. Mention it in AGENTS.md ("Use npm run build:agent to verify production builds") and the propagation path stops drifting.
Step 3 — Set envFile in .vscode/tasks.json.
Antigravity shares the VS Code task system, so going through tasks is the most reliable propagation path.
{
"version": "2.0.0",
"tasks": [
{
"label": "build (agent)",
"type": "shell",
"command": "npm run build:agent",
"options": {
"envFile": "${workspaceFolder}/.env.local"
},
"problemMatcher": []
}
]
}Step 4 — Tell the agent to gate deployment work behind test:env.
Add a pre-flight section near the top of AGENTS.md:
## Pre-flight check
Before any production build or task that calls an external API, run `npm run test:env`.
- Confirm that `Loaded keys` includes `STRIPE_SECRET_KEY` and `DATABASE_URL`.
- Confirm that `STRIPE prefix` starts with `sk_`.
If anything is missing, stop and report. Do not proceed to deployment.Once this is in place, the agent catches an empty env before the build, not after fifteen minutes of compilation. That single change has saved me more time than any other tweak in this list.
If you want to go deeper, I recommend reading the AGENTS.md authoring guide, my Antigravity .env management strategy, and how to prevent agent context drift. Together they cover the structural side of keeping environment-dependent tasks reliable.
Quick notes for Python and Go projects
The propagation paths are identical in other ecosystems; only the loaders change.
For Python projects I add python-dotenv and load explicitly at the top of the entry script: from dotenv import load_dotenv; load_dotenv('.env.local'). The diagnostic equivalent of check-env.mjs is a four-line snippet that prints os.getcwd() plus os.environ.get('STRIPE_SECRET_KEY', '<empty>')[:7]. If you are using Poetry or uv, remember that running through the package manager (uv run python main.py) injects the project's virtualenv but does not auto-load .env.local — you still need a loader call.
For Go projects I rely on github.com/joho/godotenv and gate deployment work behind a make verify-env target that uses the same idea: print the working directory, walk candidate paths, and confirm a few prefixes. Go's compile-time errors will not catch a missing variable, so the runtime check matters even more here.
The point is that the four-step setup is portable. Whatever your stack, name a single command that can prove the env is loaded, mention that command in AGENTS.md, and require the agent to run it before anything irreversible.
A two-minute checklist for the next time this happens
Whenever I get bitten again, I run through this short list before changing anything else:
- In the integrated terminal, run
echo $SHELLandwhich node— make sure you are not on the system Node - Run
npm run test:envand confirm every expected key shows up - Verify that
.env.localis owned by you and readable - For monorepos, double-check the
pnpm -worturbo runscope - Print
pwdto make sure the agent did not stay inside a subdirectory after a previous step
Software work, in the end, is a long sequence of making invisible propagation paths visible one at a time. The error message itself rarely tells you what you need; what really matters is whether you already know how the environment is being assembled behind it.
Drop scripts/check-env.mjs into your project today. The next time the same error appears, having a known first command to run takes most of the anxiety out of the moment. Thank you for reading.