Did your agent just claim the build is fixed?
How do you know what it actually fetched?
I keep getting the same Slack ping at review time.
Someone merged because a free remote box went green.
A green remote box is still not provenance.
That success is one network call that landed.
Why this FAQ exists
Agents now install packages without a real pause.
Free remote servers make that habit feel cheap.
It is not cheap in review cost.
You still own the full resolved graph.
I wrote this as a myth-busting FAQ.
Each myth gets a correction and a check.
No fake benchmarks. No vendor leaderboard.
Just files you can hash after the agent leaves.
Where scratch compute actually helps
I sometimes need a disk that is not my laptop.
A dirty resolve should not pollute my local cache.
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 scratch compute, not as an artifact registry.
The capture workflow below is the real payload.
It still works if you never use that product.
Myth 1: The agent installed it, so the lockfile is current
Did the agent edit requirements.txt or uv.lock?
Or did it only run pip install against today's index?
Those are different acts with different evidence.
One mutates your contract. One mutates a temp disk.
Corrected model: a lockfile is a pinned graph you kept.
An agent install is a one-shot resolver run you watched.
Run this before you trust the chat summary:
git status --short
git diff -- requirements.txt uv.lock poetry.lock \
package-lock.json pnpm-lock.yaml Cargo.lock go.sum
If those files are clean, you did not pin anything.
You only warmed a cache on some machine.
Ask one more question in the pull request.
Which committed file changed because of this agent?
Myth 2: pip freeze is already a lockfile
Is pip freeze a lockfile in disguise?
Only if you treat a dump as a contract.
pip freeze captures what sits in that environment.
It does not capture markers, extras, or hashes by default.
Corrected model: freeze is a snapshot of one interpreter.
A lockfile is resolver output you can replay later.
Proposed check. Label: you must run it yourself.
python -m pip freeze > /tmp/freeze-a.txt
# after the agent "helps"
python -m pip freeze > /tmp/freeze-b.txt
diff -u /tmp/freeze-a.txt /tmp/freeze-b.txt
A diff is evidence you can paste into review.
A chat sentence like "deps are fine" is not evidence.
For npm, freeze thinking fails the same way.
npm ls is not package-lock.json under review.
npm ls --all --package-lock-only
git diff -- package-lock.json
Myth 3: Same language version means the graph matches
Same Python minor on both hosts?
Maybe. Same ABI, libc, and wheel tag? Often not.
A free remote server is another OS image.
Wheels that install there may not install here.
Corrected model: the platform is part of the resolve.
Omit it, and you compare two different problems.
python - <<'PY'
import platform, sys
print("python", sys.version.replace("\n", " "))
print("impl", platform.python_implementation())
print("platform", platform.platform())
print("machine", platform.machine())
print("executable", sys.executable)
PY
Save that next to the freeze file.
Otherwise you cannot compare two green runs.
Node has the same trap with optional natives.
Record node -p process.platform and process.arch too.
node -p 'process.version+" "+process.platform+" "+process.arch'
Myth 4: No new package names means no supply-chain change
Did the top-level version string stay put?
The wheel, tarball, or tag might not have.
Yanked files get replaced on public indexes.
Mirrors drift. Rebuilds happen. Transitives move.
Corrected model: identity is name plus version plus hash.
Version equality is a weak claim, not a bill of materials.
Proposed inventory dump. Still not an SBOM tool.
python - <<'PY'
from importlib import metadata
for dist in sorted(metadata.distributions(), key=lambda d: (d.metadata["Name"] or "").lower()):
name = dist.metadata["Name"]
ver = dist.version
n = len(list(dist.files or []))
print(f"{name}=={ver} files={n}")
PY
Want a stronger check? Hash what the installer fetched.
Do not stop at the version column in pip list.
python -m pip download -d /tmp/wheels -r requirements.txt --no-deps
sha256sum /tmp/wheels/*
--no-deps is incomplete on purpose here.
It shows whether you even know your direct pins.
Myth 5: The scratch server is air-gapped enough
Is it air-gapped, or just unpaid?
You probably still hit a public index.
Agents love --upgrade and "install the missing extra."
They also love a second index URL you never reviewed.
Corrected model: a scratch server is still a network client.
Egress is part of the threat model, always.
Minimum questions before you keep the environment:
- Which index URL did the resolver hit?
- Was
--require-hashesactually on? - Did the agent add another extra index?
- Are tokens sitting in that process environment?
env | grep -E 'PIP_|UV_|NPM_|POETRY_|CARGO_|TWINE_|TOKEN|GITHUB_' || true
python -m pip config list
If that prints secrets, stop immediately.
Rotate them. Do not paste the output into a model chat.
I do not want a helpful agent to "debug auth."
I want the credential out of that box first.
Myth 6: Green tests mean the graph is mergeable
Tests exercise behavior on one interpreter.
They do not exercise provenance of every byte.
A swapped package can still pass unit tests.
A slightly newer transitive dependency can too.
Corrected model: merge when the pin, hash, and tests agree.
Not when a model replies with the word done.
Ask the PR author for three artifacts, not one screenshot:
- the lockfile diff, even if it is empty
- host and interpreter identity from both machines
- the test command that actually ran, copied verbatim
Missing any of those? The merge is a vibe check.
Vibe checks do not belong on the default branch.
The artifact: a ten-minute capture workflow
I want a folder I can keep.
Not a screenshot of a terminal from a chat log.
Label: this is a proposed local workflow.
Run it on your laptop. Then run it on the scratch host.
#!/usr/bin/env bash
# capture-graph.sh — proposed provenance snapshot
set -euo pipefail
out="${1:-./graph-capture}"
mkdir -p "$out"
{
date -u +"utc=%Y-%m-%dT%H:%M:%SZ"
echo "host=$(hostname)"
echo "who=$(whoami)"
uname -a
} > "$out/host.txt"
python - <<'PY' > "$out/python.txt"
import platform, sys
print("executable", sys.executable)
print("version", sys.version)
print("platform", platform.platform())
print("machine", platform.machine())
PY
for f in requirements.txt uv.lock poetry.lock \
package-lock.json pnpm-lock.yaml Cargo.lock go.sum; do
[[ -f "$f" ]] && cp "$f" "$out/"
done
python -m pip freeze > "$out/pip-freeze.txt" || true
python -m pip list --format=freeze > "$out/pip-list.txt" || true
command -v node >/dev/null && node -p 'process.version' > "$out/node.txt" || true
if command -v sha256sum >/dev/null; then
sha256sum "$out"/* > "$out/SHA256SUMS" || true
elif command -v shasum >/dev/null; then
shasum -a 256 "$out"/* > "$out/SHA256SUMS" || true
fi
echo "wrote $out"
Then fill this table without lying to yourself.
| Question | Laptop | Scratch host | Merge if disagree? |
|---|---|---|---|
| Interpreter version identical? | No | ||
| OS / arch recorded? | No | ||
| Lockfile hash identical? | No | ||
| Freeze diff empty? | Investigate | ||
| Require-hashes used? | No for audited trees | ||
| Index URL explicit? | No | ||
| Tests green on both? | Not sufficient alone |
If a merge cell says no, do not ship.
Fix the pin first. Then rerun the capture.
Compare the two folders like any other artifact:
diff -u laptop/graph-capture/pip-freeze.txt \
scratch/graph-capture/pip-freeze.txt
diff -u laptop/graph-capture/SHA256SUMS \
scratch/graph-capture/SHA256SUMS
Empty diffs are a story you can defend.
"The agent said it worked" is not.
What this does not prove
This script does not give you SLSA provenance.
It does not attest a builder identity.
It does not pin hashes by itself.
It does not replace uv lock, npm ci, or cargo fetch.
It will not catch a compromised index serving the same hash.
It will not catch malware that never shows in pip list.
It also does not freeze the remote image.
Tomorrow's scratch host may not be today's scratch host.
Who should skip this approach
Skip it if you already have hermetic CI with hashed pins.
Skip it if you cannot run commands on the scratch host.
Skip it if policy forbids unknown remote boxes.
Skip it if you needed a real SBOM pipeline yesterday.
Do not use a free scratch server as your release builder.
Do not paste internal package names into a public model chat.
Do not let an agent export PIP_INDEX_URL "as a convenience."
That convenience is how credentials leak into logs.
A corrected mental model
The agent is a noisy intern with root on a temp disk.
The free server is another intern's unmarked laptop.
You are still the maintainer of the graph.
Provenance is a file you keep, not a sentence you read.
Did the agent help you move faster?
Maybe. Did it document the graph? Only if you captured it.
Keep the capture folder beside the lockfile.
Or store it next to the build, not in a chat transcript.
Your call.
Just stop calling a successful pip install a lockfile.
Top comments (0)