Ever trust a chat that said tests passed?
Then watch the import fail on the box?
The chat summary still looks clean and final.
The binary on disk is a different story.
This FAQ is not about clever prompts.
It is about which file actually ran.
A free model will type python with confidence.
A free server will resolve that name somehow.
Those two steps are not one contract.
Why do we keep treating them as one?
Coding agents talk in command strings.
Unix shells talk in inodes and argv.
Do you store the inode? Or the story?
I want a check you can finish in minutes.
No dashboards. No vibes. No screenshots.
Print paths. Print exit codes. Then decide.
I draft those checks with MonkeyCode free model access.
I run the same checks on the free server option.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
The product does not freeze your PATH.
The box does not narrate sys.executable.
You still have to print both. Every time.
Hold three facts. Repeat them out loud.
python is not a binary.
If those feel obvious, good. Keep reading.
The myths below still sneak into agent logs.
python3
Developers repeat this on every new image.
"The box is modern. python means three."
Does it? Did you type type python yet?
On some images, python is missing entirely.
On others it is a leftover symlink you skipped.
On others it is not the python3 you just used.
Evidence is a command. Not a feeling.
type python
type python3
command -v python python3
readlink -f "$(command -v python3)"
python3 -c "import sys; print(sys.version.split()[0]); print(sys.executable)"
Corrected model: names are aliases.
Binaries are paths. Only paths execute.
If command -v python prints nothing, stop.
The agent's python line already failed.
The chat may still say the step is done.
Why would you believe that sentence?
You saw python3 -m venv .venv.
You saw a line that said activated.
Did the next tool call share that shell?
Many agent runners spawn a fresh shell.
Activation is a shell function plus PATH.
It dies when that shell process exits.
So the venv exists as a directory.
Your next python3 may still be system.
That is not a model bug. That is Unix.
python3 -m venv .venv
command -v python3
printf 'VIRTUAL_ENV=%s\n' "${VIRTUAL_ENV-}"
.venv/bin/python -c "import sys; print(sys.executable)"
Corrected model: a venv is a folder.
Activation is optional sugar for humans.
Absolute .venv/bin/python is the contract.
Would you pin a CI job to bare python?
I would not. Neither should the agent.
The file starts with #!/usr/bin/env python.
Looks neat. Looks intentional. Looks false.
Who invoked the file, and with which argv?
python3 app.py ignores the shebang.
bash app.py ignores the shebang too.
./app.py uses it, if the mode allows exec.
head -n 1 app.py
ls -l app.py
command -v env
/usr/bin/env python -c "import sys; print(sys.executable)" || true
./app.py || echo "direct exec failed"
If the file is not executable, .
./app.py never ran on that box.
Maybe the agent used python3 app.py.
Then the shebang never mattered at all.
Corrected model: shebang is a kernel feature.
It applies to execve of that file path.
It is not a hint the interpreter rereads.
pip install hits the interpreter you used
This one burns hours and lockfiles.
python3 was one prefix. pip was another.
Install succeeded. Import failed. Chat shrugged.
It is not a mystery. It is two binaries.
Did pip --version mention the same path?
command -v pip pip3 python3
python3 -m pip --version
pip --version || echo "bare pip missing"
python3 -c "import sys; print('\n'.join(sys.path[:4]))"
Always python3 -m pip on a throwaway box.
Or .venv/bin/python -m pip. Never bare pip.
Bare pip is how silent no-ops get shipped.
Corrected model: pip belongs to one interpreter.
Installing with the wrong pip is a miss.
The right interpreter never saw the wheel.
The model said all tests passed.
Did pytest print a real exit code?
Did you capture $?, or only the prose?
Chat UIs compress stdout. They hide stderr.
They paraphrase tracebacks into calm English.
They do not store sys.executable for you.
python3 -c "import sys; print(sys.executable)"
.venv/bin/python -m pytest -q
echo "exit:$?"
Corrected model: success is an exit code.
Success also names the interpreter path.
A sentence from a model is a rumor.
Would you merge on a rumor in code review?
Then do not merge on a chat recap either.
Do not argue with the chat window.
Write a file. Run the file. Keep the file.
This script is a labeled example. Run it locally on the target box.
#!/usr/bin/env bash
set -u
echo "=== shell ==="
printf 'pid=%s user=%s pwd=%s\n' "$$" "$(id -un)" "$PWD"
echo "=== PATH head ==="
printf '%s\n' "$PATH" | tr ':' '\n' | head -n 8
echo "=== names ==="
for n in python python3 pip pip3 pytest; do
if command -v "$n" >/dev/null 2>&1; then
printf '%-8s -> %s\n' "$n" "$(command -v "$n")"
else
printf '%-8s -> MISSING\n' "$n"
fi
done
echo "=== python3 details ==="
if command -v python3 >/dev/null 2>&1; then
python3 - <<'PY'
import sys, os
print("executable", sys.executable)
print("prefix ", sys.prefix)
print("base ", getattr(sys, "base_prefix", ""))
print("version ", sys.version.split()[0])
print("venv? ", sys.prefix != getattr(sys, "base_prefix", sys.prefix))
print("VIRTUAL_ENV", os.environ.get("VIRTUAL_ENV", ""))
PY
python3 -m pip --version || echo "python3 -m pip failed"
fi
echo "=== venv dir ==="
if [ -x .venv/bin/python ]; then
.venv/bin/python -c "import sys; print('venv_exec', sys.executable)"
.venv/bin/python -m pip --version || true
else
echo "no .venv/bin/python"
fi
echo "=== reminder ==="
echo "print exit:\$? after every real test command"
Save it next to the project.
chmod +x interp_audit.sh.
Run it in the same cwd the agent used.
Then fill the table. The table is the artifact.
The chat is not the artifact. Never was.
| You saw in chat | Likely myth | Run this | Trust this instead |
|---|---|---|---|
| "python is available" | Myth 1 | type python; type python3 |
The printed path |
| "venv activated" | Myth 2 | printf '%s\n' "${VIRTUAL_ENV-}" |
.venv/bin/python |
| "script is executable" | Myth 3 | head -n 1 app.py; ls -l app.py |
argv plus mode bits |
| "package installed" | Myth 4 | python3 -m pip show NAME |
that command's output |
| "tests passed" | Myth 5 | echo $? andsys.executable |
exit code and path |
Compare the left column with the right.
The left column is theater from a recap.
The right column is what the box did.
Want a wrong-pip failure to show up fast?
Use this labeled, unexecuted example.
import sys
print("CANARY", sys.executable)
try:
import requests # swap for the package you expected
print("import_ok", getattr(requests, "__file__", "?"))
except ImportError as exc:
print("import_fail", exc)
raise SystemExit(2)
Run it two ways. Read both lines.
python3 canary_interp.py; echo exit:$?
.venv/bin/python canary_interp.py; echo exit:$?
Do those two CANARY paths match?
If not, which path did the agent mean?
If you cannot answer, you cannot ship.
sys.executable for the test command?python3 -m pip --version right now?echo $? print after pytest?
If any answer is "the chat said so," stop.
You are debugging prose. Not a process.
This audit does not pin a lockfile.
It does not replace uv or Poetry flows.
It does not freeze a base image either.
Shims from pyenv resolve later than you think.
Print sys.executable anyway. Do not guess.
The script assumes a Unix shell and head.
Windows needs where and py -0p. Skip this file there.
Free servers get recycled without warning.
Your audit is a snapshot of one shell.
Rerun it after every fresh session.
Who should skip this whole approach?
.venv/bin/python everywhere.
A free model will still invent a python name.
A free server will still pick a default binary.
The audit is a bridge. It is not a guarantee.
I trust three lines. Only three.
Everything else is a story about commands.
Stories are cheap. Paths are not cheap.
Ask the chat for a recap if you want color.
Then ignore the recap. Open the audit file.
If those two paths disagree, the myth already won.