Did your agent inherit a lockfile, or just a vibe?
I keep seeing five claims in review threads. They sound harmless until a lockfile drifts overnight. This FAQ names each claim and the matching check.
You can run every command without a vendor. I still want the host to fail closed. Hope is not a supply chain control.
Why this FAQ exists
Remote agent hosts still feel like extra laptops. They are not even close to that. A familiar filename in chat is not provenance.
A free model can draft shell commands very fast. It cannot pin your compiler for you. It cannot hold your production secrets either.
Who is this for in actual practice? People who mix local git with remote loops. If that describes you, keep reading here.
Where a free model and free server fit
I sometimes park the loop on MonkeyCode for drafts.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
The relevant bits are free model access and a free server option. I treat both as disposable sandboxes, nothing more. I do not paste production secrets into either place.
Delete that product name tomorrow if you want. The five myths still stand without it.
Myth 1: The host already holds my real working tree
The claim
The agent is already inside my real repo.
Why people repeat it
The chat shows filenames you recognize at a glance. Familiar paths feel like proof of a checkout. Screenshot energy beats a boring git status.
What is actually true
A checkout can be shallow or just stale. It can point at the wrong fork. It can miss the lockfile you swore existed.
Corrected mental model
The host has some tree. You prove which tree. No proof, no install step.
Try this before any package command:
# proposed preflight — unexecuted until you run it
git rev-parse --show-toplevel
git remote -v
git status --porcelain=v1
git rev-parse HEAD
git branch --show-current
git log -1 --oneline
test -f package-lock.json || test -f pnpm-lock.yaml \
|| test -f poetry.lock || test -f Cargo.lock || test -f go.sum
See a dirty tree? Stop the loop right now. See no lockfile? Stop even harder than that.
Would I merge this HEAD on my laptop? If the answer is no, the agent does not install.
Myth 2: Dropping a .env onto the host is just faster
The claim
The model needs credentials, so copy .env.
Why people repeat it
Local scripts already load dotenv without drama. Copying the file feels consistent and kind. Nobody wants to retype tokens by hand.
What is actually true
A remote disk is not your laptop volume. Prompt logs capture whatever the model reads. Transcripts get pasted into tickets later.
Corrected mental model
Inject one short-lived environment variable. Never persist secrets inside the workspace. If the model can cat it, it will.
Proposed pattern, not a live secret:
# proposed: one scoped token, not a file dump
export GH_TOKEN="${GH_TOKEN:?set a short-lived token in your own shell}"
# do not do this:
# scp .env agent-host:~/project/.env
Redact before you paste any log:
# proposed redaction filter for a transcript
sed -E 's/(TOKEN|SECRET|PASSWORD|KEY)[^=]*=.*/\1=***REDACTED***/Ig'
Need a secret for tests on that host? Use a real secret store, not a file. Would you commit that .env to main? Then do not upload it.
Myth 3: Lockfiles are hints. The model can just install
The claim
npm install is fine because the model knows versions.
Why people repeat it
Fresh installs appear to work in the chat. The app boots for a demo. Nobody diffs node_modules after that.
What is actually true
Unlocked installs drift without any apology. Transitive versions move under your feet. Your laptop and the host diverge before lunch.
Corrected mental model
The lockfile is the contract, not a comment. Install is a replay of that contract. Floating installs are bugs with extra steps.
# proposed: fail closed if the installer wants to mutate the lock
npm ci --ignore-scripts
# or
pip install --require-hashes -r requirements.txt
# or
cargo fetch --locked
# or
go mod download
Add a tiny guard test. Label it proposed until it runs in CI.
# proposed test: tests/test_lockfile_present.py
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
LOCKS = [
ROOT / "package-lock.json",
ROOT / "pnpm-lock.yaml",
ROOT / "yarn.lock",
ROOT / "poetry.lock",
ROOT / "Cargo.lock",
ROOT / "go.sum",
ROOT / "requirements.txt",
]
def test_a_lockfile_exists():
assert any(p.is_file() for p in LOCKS), "no lockfile in the tree"
def test_dotenv_is_not_committed():
assert not (ROOT / ".env").is_file(), ".env must not live in the repo"
Did the model skip ci and float the graph? That is a defect. Do you want that graph in production? Then stop treating install as improvisation.
Myth 4: The free model will pick the same toolchain tomorrow
The claim
It found Python today, so it will find Python again.
Why people repeat it
One lucky python3 --version lands in the transcript. People screenshot the line like a pin. PATH luck becomes tribal memory.
What is actually true
Free model routing can change without a memo. PATH can change after an image rebuild. The word "Python" is not a version pin.
Corrected mental model
Pin the toolchain inside the repo itself. Read those pins on the host. Abort on the first mismatch you see.
# proposed toolchain pin files in the repo
# .python-version -> 3.12.6
# .nvmrc -> 20.17.0
# rust-toolchain.toml -> whatever you actually ship
python_wanted=$(tr -d ' \n' < .python-version)
python_got=$(python3 -c 'import sys; print("%d.%d.%d" % sys.version_info[:3])')
test "$python_wanted" = "$python_got" || {
echo "python mismatch: wanted $python_wanted got $python_got"
exit 1
}
Ask a rude question before you trust tests. If the versions differ, whose tests were those? I do not ask a free model to choose a compiler. I ask it to run the pinned one.
Myth 5: The agent can wear my git identity
The claim
Let it commit. It already has my name.
Why people repeat it
Green diffs look ready for history. People want the loop to close tonight. Manual git config feels like ceremony.
What is actually true
Commits carry author, email, and often a token. A remote agent with your global git config is you. That identity stays in history forever.
Corrected mental model
Give the agent a machine identity, not yours. Label the commits so humans can grep them. Keep your personal token off that host.
# proposed: local-only git identity for the agent
git config --local user.name "agent-bot"
git config --local user.email "agent-bot@invalid.example"
git config --local commit.gpgsign false
git config --local --get-regexp '^user\.'
Also block an accidental push from the sandbox:
git remote remove origin 2>/dev/null || true
# or freeze the URL if you still need fetches
git remote set-url --push origin no-push://disabled
Would you let a contractor commit as you? Then do not let a loop do it. History is not a chat window you can edit.
Artifact: one preflight script and a decision table
Here is a single script I would drop at the repo root. Treat it as proposed until you run it. It encodes the five myths as exit codes.
#!/usr/bin/env bash
# scripts/agent_preflight.sh
# proposed workspace checks for a remote agent host
set -euo pipefail
die() { echo "preflight: $*" >&2; exit 1; }
git rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|| die "not a git work tree"
echo "HEAD=$(git rev-parse HEAD)"
echo "BRANCH=$(git branch --show-current || echo detached)"
echo "REMOTE=$(git remote get-url origin 2>/dev/null || echo none)"
test -z "$(git status --porcelain)" || die "dirty tree; refuse to install"
shopt -s nullglob
locks=(package-lock.json pnpm-lock.yaml yarn.lock poetry.lock Cargo.lock go.sum requirements.txt)
found=0
for f in "${locks[@]}"; do
[[ -f $f ]] && found=1
done
[[ $found -eq 1 ]] || die "no lockfile"
test ! -f .env || die ".env present; remove it from the workspace"
test ! -f credentials.json || die "credentials.json present; remove it"
if [[ -f .python-version ]]; then
wanted=$(tr -d ' \n' < .python-version)
got=$(python3 -c 'import sys; print("%d.%d.%d" % sys.version_info[:3])')
[[ "$wanted" == "$got" ]] || die "python $got != $wanted"
fi
git config --local --get user.email | grep -q 'invalid.example' \
|| die "refusing human git identity on an agent host"
echo "preflight ok"
Decision table
| Situation | Do this | Do not do this |
|---|---|---|
| Need packages | Replay the lockfile (npm ci, --locked) |
Floating install because the model suggested it |
| Need a secret | Inject one env var with a tight scope | Copy .env onto the host |
| Need a commit | Local user.email for a bot |
Reuse your personal user.name
|
| Need a model | Disposable free model for draft commands | Treat the transcript as provenance |
| Need a box | Disposable free server for the loop | Assume yesterday's PATH still exists |
Print the table. Tape it above the terminal. I am not kidding about that.
A ten-minute workflow I actually follow
- Clone a known SHA onto the host.
- Run
scripts/agent_preflight.shbefore any install. - Let the model propose commands only, in chat.
- Execute installs yourself from the lockfile.
- Run the two pytest guards on that tree.
- Read
git status --porcelainwith your own eyes. - Throw the workspace away when the loop ends.
Notice what is missing from that list? Blind sudo. A copied .env. Trust in tomorrow's PATH.
If the free server vanishes after the session, I lose a sandbox. I do not lose the source of truth. That is the whole point.
Limitations
This workflow does not make a model honest. It only fails closed on the host. That is a smaller promise than it sounds.
It will not catch a token already sitting in chat history. It will not pin GPU drivers you never declared. It will not sign releases for you.
It assumes you own the repo and can add scripts. Fork-and-forget setups need a different gate. Free model output stays non-deterministic after preflight.
I have not published timings in this FAQ. Run the script on your machine. Trust that clock, not mine.
Who should not use this
Do not send production customer data to any remote model. Do not send production customer data to a free server. Those are the same rule twice.
If you work in a regulated environment, stop here. Use an approved private runner instead. This FAQ is not an audit program.
If your threat model includes prompt logs as evidence, this is not enough. You need a real secret manager and a real trail. A sed filter is not that trail.
If you cannot throw the workspace away, you are not in a sandbox. You are on a shared box. Leave before the next install.
What I want you to remember
The model is not your package manager. The host is not your keychain. The chat is not your git identity.
Five myths. One script. Zero excuses to copy .env.
Run the preflight once on the next remote session. Then argue with the transcript, not with hope.
Top comments (0)