Did the token die with the chat tab?
I keep seeing the same five confident claims. They show up after a successful agent run.
The chat looks confident, but the box disagrees. This FAQ is about persistence across agent sessions.
It is not about TTY tricks at all. It is not about which python binary answered.
Those are other problems for other days. This one is simpler, and it is meaner.
What still exists after the model stops talking?
Why this FAQ exists
Agents mix three stores. People treat them as one store.
- The model's context window
- The shell process environment
- Files sitting on the server disk
Those three drift apart very fast. A remote box makes that drift obvious.
Your laptop PATH is not on that box. A model can narrate pip install in fluent prose.
Narration is not site-packages. A closed browser tab is not rm -rf.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
I use MonkeyCode when I want a free model and a free server in one loop. I do not name models, quotas, hardware, or how long that free option lasts. The checks below work on any POSIX box you can actually exec on.
The artifact: dump the three stores
Do not argue with the transcript. Print state instead.
I keep this script in the repo root. I paste it as a command, not as a question.
Label this as a workflow to run. It is not a benchmark I already ran.
#!/usr/bin/env bash
# session_audit.sh — proposed check, run on the remote box
set -euo pipefail
echo "=== identity ==="
whoami
id
hostname
pwd
date -u +%Y-%m-%dT%H:%M:%SZ
echo "=== process env (redacted) ==="
python3 - <<'PY'
import os, re
secret = re.compile(r"(KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)", re.I)
for k, v in sorted(os.environ.items()):
if secret.search(k) or secret.search(v or ""):
print(f"{k}=<redacted len={len(v)}>")
else:
print(f"{k}={v}")
PY
echo "=== claimed vs installed (python) ==="
python3 - <<'PY'
import importlib.util, sys
print("executable:", sys.executable)
print("version:", sys.version.replace("\n", " "))
print("path:")
for p in sys.path:
print(" ", p)
wanted = ["fastapi", "pytest", "requests", "numpy"]
for name in wanted:
spec = importlib.util.find_spec(name)
print(f"import {name}: {'FOUND' if spec else 'MISSING'}")
PY
echo "=== git identity on THIS box ==="
git config --show-origin --get-regexp 'user\.(name|email)' || true
git status -sb || true
echo "=== files the chat may have invented ==="
ls -la .env .env.local .netrc 2>/dev/null || true
test -f .env && echo "WARNING: .env exists on disk"
Run it twice. Once now, once in a later turn.
Compare the two dumps yourself. Do not compare two chat summaries.
Myth 1: Closing the tab wipes the box
Does killing the browser kill the server disk?
Usually no. A tab is only a client.
A server is a machine with a filesystem. Files you wrote can still sit there.
So can .env. So can a stray key file. So can a venv the model built.
Corrected mental model
- UI session is not a process
- Process state is not disk state
- Disk state is not model memory
Evidence check
stat -c '%n %y %s' .env credentials.json 2>/dev/null || echo "no secret files found"
find . -maxdepth 2 -name '*.pem' -o -name 'id_rsa' 2>/dev/null
If stat prints a timestamp, the file survived. The chat did not need to stay open.
Myth 2: The model knows the library, so the box has it
The model can write FastAPI code from training. That is not pip show fastapi.
I ask a blunt question instead. Can this interpreter import it right now?
python3 -c "import fastapi, sys; print(sys.executable, fastapi.__file__)"
command -v pytest || echo "pytest not on PATH"
If that fails, the essay still looks fine. The runtime is still empty.
Corrected mental model
- Training data is not
site-packages - A generated
requirements.txtis not an installed venv -
pip installin the transcript is not a finished install
Evidence is the import. Not the prose around it.
Did the install even target this interpreter? Pin it.
python3 -m pip --version
python3 -m pip show pytest || true
readlink -f "$(command -v python3)"
Myth 3: export in turn 3 is still there in turn 9
Did you export DATABASE_URL in an earlier command?
That export lived in that shell process. A new tool call may be a new process.
Some agent runners reuse one shell. Some spawn bash -lc every time.
You cannot guess from the chat tone. Guessing is how tokens leak into stories.
Corrected mental model
-
exportis process state only - A later tool call may spawn a fresh shell
- Parent env is not a promise
Evidence check
Use a canary, not a memory.
# turn A
export SESSION_CANARY=alive-$$
echo "canary_pid=$$"
# turn B, later, no story — just print
python3 -c 'import os; print(os.environ.get("SESSION_CANARY", "MISSING"))'
If turn B prints MISSING, stop trusting spoken env. Write a file, or use a real secret store.
Need the value in later commands? Do not export and pray.
umask 077
printf 'SESSION_CANARY=%s\n' "alive" > .session_canary
# later
set -a
# still do not source untrusted files blindly
python3 -c 'from pathlib import Path; print(Path(".session_canary").read_text())'
Myth 4: A .env in the chat is the process environment
The agent often writes a .env example. Then it says the app is configured.
Who loaded it? Which process? With what parser?
dotenv is not automatic. Your framework may ignore the file until boot.
A fenced block in chat is not even a file yet. Ask test -f before you relax.
Corrected mental model
- File on disk is not
os.environ - A quoted block in chat is not a file
-
source .envbreaks on spaces, quotes, and$
Evidence check
Two booleans. They often disagree.
python3 - <<'PY'
from pathlib import Path
import os
p = Path(".env")
print("dotenv_exists", p.is_file())
print("environ_has_DATABASE_URL", "DATABASE_URL" in os.environ)
if p.is_file():
print("dotenv_bytes", p.stat().st_size)
print("dotenv_mode", oct(p.stat().st_mode))
PY
If the file exists and the key is missing, nobody loaded it. If the key exists and the file does not, it came from the process, not from .env.
Myth 5: git config on the box is your laptop identity
Did the agent commit as you?
Check user.email on the server. Then check author on the latest commit.
Those two strings can differ. Either one can be wrong.
git config --show-origin --get user.email || echo "no email configured"
git config --show-origin --get user.name || echo "no name configured"
git log -1 --format='%an <%ae>%n%cn <%ce>' 2>/dev/null || echo "no commits"
A free box may have leftover global config. It may have none at all.
Your laptop ~/.gitconfig did not travel with the prompt. A forged author is still a git object.
Corrected mental model
- Laptop git config stays on the laptop
- Commit author is local to that user or that repo
-
git statusin chat is notgit statuson disk
Do not push until the audit matches the person you want. Then check git remote -v too.
git remote -v
git config --get-regexp 'credential|insteadOf' || true
Decision table I actually use
| Claim in chat | Store it lives in | Command that falsifies it |
|---|---|---|
| "I installed pytest" | disk / venv | python3 -c "import pytest" |
| "TOKEN is set" | process env | python3 -c "import os; print('TOKEN' in os.environ)" |
| ".env is loaded" | file vs process | file exists and key in os.environ
|
| "we are on main" | git objects | git status -sb && git rev-parse --short HEAD |
| "that file is gone" | disk | stat path |
| "this is your email" | git config | git config --show-origin --get user.email |
| "install finished" | process exit + disk |
echo $? then pip show
|
If the command disagrees, the chat is wrong. Keep the command output.
A tiny workflow for a free model plus a free server
I do not start with a feature request. I start with inventory.
- Open a remote shell on the free server.
- Run
session_audit.shbefore any install. - Ask the model for a change set, not a story.
- Apply the change. Run the audit again.
- Diff the two dumps. Keep only proven state.
The free model is for proposing diffs. The free server is for executing checks.
Do not let the model grade its own persistence. It cannot see a process it no longer owns.
Diff without poetry:
./session_audit.sh | tee /tmp/audit-before.txt
# ... agent edits ...
./session_audit.sh | tee /tmp/audit-after.txt
diff -u /tmp/audit-before.txt /tmp/audit-after.txt || true
The diff is the lesson. The victory message is not.
Limitations
This audit is local truth. It is not a security review.
It will not tell you if another tenant can read the disk. I have no evidence about isolation on any vendor box.
It will not prove a package stays after a rebuild. I did not measure lifetime, and I will not invent one.
Redaction by regex misses values without KEY in the name. Treat dumps as sensitive anyway.
python3 in the script may not be the app interpreter. Point it at the real venv when you have one.
# proposed, only if you already know the venv path
./.venv/bin/python -c "import sys; print(sys.executable)"
Timezone in date -u is UTC. Your app logs may not be. find above is shallow on purpose.
This assumes POSIX bash, python3, and git. Windows shells need a different dump.
Who should not use this approach
Skip this if you must park production secrets on the box. A free remote server is the wrong place for those.
Skip this if you need an SLA on disk. I did not claim files last any number of days.
Skip this if you cannot run commands, only chat. Then you have no evidence at all.
Skip this if the box is shared and you cannot control umask and file modes.
What I want you to remember
The transcript is a story. The dump is a measurement.
Ask one question after every "done." Which store changed?
If you cannot name the store, you do not have the result. Context, process, and disk are not synonyms.
If you already have a remote shell, run the script today. If you are on MonkeyCode's free server, paste it there and keep the dump. Trust the second copy, not the victory message.
Top comments (0)