Did your coding agent export that variable for real? Or did the model only type the word export? That mismatch wastes whole evenings on green-looking chats.
Chat transcripts look like a living bash session. They are usually a pile of disconnected process claims. Who inherited FOO after the story ended?
Why this FAQ exists
Coding agents paste export, cd, and dotenv lines constantly. Then they talk as if later commands inherited that state.
Did anyone run printenv in the same process? The model did not. It only completed more tokens.
This FAQ is not another take on AI replacing developers. It is a POSIX checklist for borrowed agent boxes. Steal the checks. Ignore the narration.
The corrected mental model
Treat every agent turn as a fresh claim, not a shell. Prove three facts after any setup speech.
-
CWD: what
pwd -Pprints in this process - ENV: what the next process actually inherited
-
FILES: what
test -fsays on disk right now
Conversation memory is not execve state. If you cannot paste those three outputs, you do not know. Ask the kernel. Stop asking the transcript.
Myth 1: "It typed export, so FOO is set"
You will hear this claim all week. "I exported DATABASE_URL. The tests used it." Cute story. Where is the dump?
Demand evidence from the same process, not a later paragraph:
printenv DATABASE_URL
python3 -c "import os; print(repr(os.environ.get('DATABASE_URL')))"
Empty on both lines? Then export never landed here. Or it landed in a child that already exited. That is normal. That is POSIX.
Try this tiny lie detector on your own laptop first:
bash -lc 'export FOO=from-child; printenv FOO'
printenv FOO || echo "parent never saw FOO"
The inner shell can print from-child. Your outer shell still has nothing. Agent tool calls often look like that inner bash -lc. The chat still talks like a login session.
Corrected model: export in a fenced block is a suggestion. It becomes real only inside a process that ran it. Children of that process may inherit it. A later turn may not. CI will not. Your laptop will not, unless you exported it there too.
Myth 2: "It wrote .env, so the app loaded it"
Agents love creating .env files. Django does not auto-load them. Flask does not either. Node does not, unless you wired a loader.
Ask two boring questions. Did a loader run? From which working directory?
test -f .env && echo "file exists" || echo "no file"
python3 - <<'PY'
from pathlib import Path
cwd = Path.cwd().resolve()
p = cwd / ".env"
print("cwd", cwd)
print("exists", p.is_file(), "bytes", p.stat().st_size if p.is_file() else 0)
print("DOTENV_LOADED", __import__("os").environ.get("DOTENV_LOADED"))
PY
A file on disk is not an environment. Something must parse it. Name that something. set -a; source .env; set +a is one loader. python-dotenv is another. A random open('.env') in chat is neither.
Corrected model: If you cannot name the loader, assume nothing loaded. test -f .env only proves a path. It does not prove os.environ.
Myth 3: "It cd'd last turn, so we are still there"
The transcript shows a confident hop:
cd /tmp/work && ls
Next turn the agent runs pytest. Which directory is current? Yours? The box $HOME? /tmp/work from a dead child?
Check. Do not infer from story order.
pwd -P
readlink -f /proc/self/cwd 2>/dev/null || true
ls -ld .
CWD is per process. Chat turns are not && unless the product documents a persistent shell. A workspace directory may survive. Your next prompt may still start in $HOME. Those are different facts. Prove both.
Corrected model: cd in a previous fence is archaeology. pwd -P is evidence. Keep the evidence.
Myth 4: "The reply printed a secret, so the process has it"
This is the inverse myth, and it is worse. The model echoes .env contents. People think the runtime exported those keys.
Printing is not putenv. A leak in a transcript is a security problem. It is not proof of injection into os.environ.
python3 - <<'PY'
import os
keys = ("AWS_SECRET_ACCESS_KEY", "DATABASE_URL", "STRIPE_SECRET_KEY")
for k in keys:
v = os.environ.get(k)
print(k, "set" if v else "missing", "len", len(v) if v else 0)
PY
Print lengths. Never reprint values. If you must pass a secret, inject it outside the prompt. Then verify with a length check in the next process.
Corrected model: Stdout can recite fiction. os.environ.get cannot pretend as easily. Trust the map. Distrust the prose.
Myth 5: "The borrowed box kept my exports overnight"
Remote agent boxes feel like pets. They are cattle. People assume yesterday's export still lives. Or that npm install filled a cache they own.
Ask the box, not the model:
hostname; id; pwd -P
printenv | wc -l
ls -ld "$HOME" /tmp "$PWD"
Write a sentinel. Read it next session. If the file is gone, your mental model was wrong. That is useful data. That is not a failure of "AI skill."
Corrected model: Persistence is a product claim, not a Unix default. Treat each session as a cold start until a sentinel file answers you.
Artifact: a 30-second env/cwd audit
Do not trust a green sentence. Run this proposed check. Label it as unexecuted until you run it. Save audit_env_cwd.py, then run it twice: laptop and box.
#!/usr/bin/env python3
"""Compare claimed shell state with the real process."""
from __future__ import annotations
import json
import os
from pathlib import Path
SENTINEL_KEYS = (
"DATABASE_URL",
"CI",
"VIRTUAL_ENV",
"DOTENV_LOADED",
)
def main() -> None:
cwd = Path.cwd().resolve()
dotenv = cwd / ".env"
report = {
"cwd": str(cwd),
"pid": os.getpid(),
"ppid": os.getppid(),
"path_entries": len(os.environ.get("PATH", "").split(":")),
"env_key_count": len(os.environ),
"dotenv_exists": dotenv.is_file(),
"dotenv_loader_claimed": os.environ.get("DOTENV_LOADED"),
"keys": {k: bool(os.environ.get(k)) for k in SENTINEL_KEYS},
"home_is_cwd": Path.home().resolve() == cwd,
}
print(json.dumps(report, indent=2, sort_keys=True))
if __name__ == "__main__":
main()
Wrapper that catches the "I already exported it" story:
#!/usr/bin/env bash
set -euo pipefail
# Proposed check. You run it. No stored scores here.
python3 audit_env_cwd.py > /tmp/audit-before.json
# If the agent "exported" something, do it in THIS process.
# Example only. Do not paste real secrets into chat.
export DOTENV_LOADED=1
python3 audit_env_cwd.py > /tmp/audit-after.json
python3 - <<'PY'
import json
from pathlib import Path
b = json.loads(Path("/tmp/audit-before.json").read_text())
a = json.loads(Path("/tmp/audit-after.json").read_text())
print("cwd stable", b["cwd"] == a["cwd"])
print("dotenv file", a["dotenv_exists"])
print("DOTENV_LOADED before", b["keys"]["DOTENV_LOADED"])
print("DOTENV_LOADED after", a["keys"]["DOTENV_LOADED"])
PY
You now have a diff. Not a vibe. If DOTENV_LOADED is true only after you exported it, the chat export was theater.
Decision table
Use this when the agent narrates setup. Empty cells mean you skipped the science.
| Agent claim | Command that can falsify it | If it fails, believe |
|---|---|---|
export FOO=bar |
python3 -c "import os; print(os.environ.get('FOO'))" |
FOO never entered this process |
"I wrote .env" |
test -f .env && wc -c .env |
No file, or a zero-byte file |
"App loaded .env" |
loader flag or framework hook | File is not environ |
"cd /tmp/work" |
pwd -P |
Still $HOME or workspace root |
| "Var persists next turn" | rerun the audit in a new prompt | Cold start |
| "Same as my laptop" | diff two JSON audits | Different machine, different env |
Pin the table beside your prompt notes. Fill it once per session. Then argue with numbers.
Where a free model and a free server help
I want a second machine. I do not want a second paragraph from the same chat. Laptop env is yours. A remote box is not. Diff those JSON files. That is the whole trick.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode offers free model access and a free server option. I treat that server as a disposable POSIX box for the second audit. I do not treat it as CI. I do not treat it as my laptop. I do not invent quotas, model names, or hardware I cannot see.
If that option is unavailable, the audit still works. Run it on any VM you already have. The method does not depend on a brand.
What this does not prove
This audit does not prove tests passed. It does not prove a package landed in your lockfile. It does not prove a daemon survived a disconnect. It does not measure model quality, and I will not fake scores.
It also does not tell you CPU, RAM, or retention rules I was not given. Short checklist. Honest gaps. Keep both.
Who should not use this approach
Skip this if you already have a documented persistent shell. Skip this if you cannot run commands on the box at all. Skip this if compliance forbids unknown remotes.
Do not paste production secrets into a chat, free or not. Do not use a borrowed server as a secret store. Do not skip real CI because a box narrated green.
Linux and macOS are the target here. Windows env blocks play by other rules. Do not copy these commands there blindly.
Closing question
Next time an agent says "I exported it," what will you run first? printenv in the same process? Or another prompt that repeats the claim?
Steal the script. Diff two machines. If you want a spare box for that second JSON file, MonkeyCode's free server option is one place to try the same audit.
Keep the JSON. Throw away the vibes.
Top comments (0)