Have you ever trusted a model's exit code, then watched the same script explode over SSH? I lost a messy weekend to a missing requirements file that was never supposed to exist. The worker looked like ordinary Python on disk, and that was the trick that wasted my attention. Those dependencies were real, but they were hiding in a comment fence at the top of the file.
I was drafting a tiny polling worker with free model access, then leaving that process on a free remote server overnight. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I am writing as a user of those free-model and free-server options, not as a lab report. You will not find latency charts here, because I did not run a formal study and I will not invent one.
Hour 0: two shells, one undeserved green check
The chat environment executed the file with a helper that understands inline script metadata. My SSH session on the free server executed the same path with plain python3. Did I notice that split while I was moving fast and feeling clever? I did not notice, because a zero exit code looks like proof when you want to ship. I copied the file with scp, started it, and got ModuleNotFoundError: No module named 'httpx'.
Why would a missing module feel like a server problem instead of a packaging problem? Because the transcript had already installed something I never watched. There was no requirements.txt in the tree. There was no pyproject.toml either, which made me even more confident that the box was incomplete. I did the tired thing and invented dependencies from memory and from the order of tracebacks.
What I tried, including the embarrassing parts
I will not rewrite this as a clean tutorial. Here is the real sequence I followed, complete with wasted motion.
- I grepped the repo for
requirementsandpyproject, then declared the project unpackaged. - I ran
pip3 install httpxinto whatever interpreterpython3happened to name that hour. - I added another import, watched it fail, and installed that second name as a fresh guess.
- I restarted the worker once, then edited the file again and forgot to restart anything.
- I blamed the free server for being sparse, because the chat window had looked healthy and complete.
Does that list feel too familiar for a supposedly careful engineer? It should, if you have ever trusted a transcript more than sys.executable and a process id. The loop I kept repeating looked almost reasonable in the scrollback.
python3 worker.py
echo $?
which python3
python3 -c "import sys; print(sys.executable); print(sys.path[:3])"
pip3 install httpx pydantic
python3 worker.py
ps aux | grep -E 'worker|python' | grep -v grep
The identity one-liner was the useful command. Of course I ran it last, after the guessing had already made a mess.
What actually broke
The header was not a comment I could skip. It was a PEP 723 inline metadata block, the little TOML fence that a script runner reads before it builds an ephemeral environment. Plain python3 does not read that fence at all. It executes the file and hopes every import already exists on sys.path.
Here is the shape of the example worker the model had written. Treat this as a labeled field file, not as a production service with traffic.
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "httpx",
# "pydantic",
# ]
# ///
from __future__ import annotations
import os
import time
import uuid
import httpx
from pydantic import BaseModel
BOOT_ID = uuid.uuid4().hex[:8]
class Pulse(BaseModel):
ok: bool
source: str
boot: str
def main() -> None:
url = os.environ.get("PULSE_URL", "https://example.com")
response = httpx.get(url, timeout=10.0)
pulse = Pulse(ok=response.status_code == 200, source=url, boot=BOOT_ID)
print(pulse.model_dump_json(), flush=True)
time.sleep(1)
if __name__ == "__main__":
main()
Why did the chat succeed on the first try? Its runner consumed the fence, installed those two packages somewhere disposable, and only then executed the file. Why did the free server fail with a boring traceback? I invoked the interpreter directly, with no fence reader and no ephemeral environment. Python cannot say "this file expects a script runner." It can only say a module is missing, which is true and almost useless.
I had a second break hiding under the first one, and it wasted the next block of hours. After httpx finally imported, I edited the worker and ran it again without checking owners of the log file. An old process was still printing lines I treated as fresh evidence. Have you tailed a log that belonged to a pid you were no longer running? You will, if your debug loop is tail -f without a boot id.
Hour 18: the fake freeze I almost committed
Around hour eighteen I did something that looked responsible and was still wrong. I ran pip3 freeze on the machine where the chat runner had succeeded, then I copied that freeze onto the free server as requirements.txt. Did that feel like engineering? It felt like engineering, which is why it was dangerous.
The freeze contained packages the worker never imported. It also missed the requires-python constraint from the fence, so I learned nothing about interpreter age. I almost committed a ghost file that would have lied to every future install. The honest move was smaller: read the first twenty lines, extract the fence, and refuse to invent a manifest.
The artifact: identity first, metadata second
I now drop a tiny sidecar next to any agent-written script before I argue with a remote box. It is boring on purpose, because boring printouts beat confident guesses. Save this as runtime_id.py and invoke it with the same prefix you use for the worker.
# runtime_id.py — labeled field tool, not a benchmark harness
from __future__ import annotations
import hashlib
import os
import pathlib
import subprocess
import sys
def git(*args: str) -> str:
try:
result = subprocess.run(
["git", *args],
check=False,
capture_output=True,
text=True,
timeout=5,
)
except (FileNotFoundError, subprocess.TimeoutExpired):
return "unavailable"
return (result.stdout or result.stderr or "").strip() or "empty"
def fence_hash(path: pathlib.Path) -> str:
text = path.read_text(encoding="utf-8")
start = text.find("# /// script")
end = text.find("# ///", start + 5)
if start < 0 or end < 0:
return "no-pep723-fence"
payload = text[start : end + 5]
return hashlib.sha256(payload.encode()).hexdigest()[:12]
def main() -> None:
target = pathlib.Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else pathlib.Path("worker.py")
print(f"cwd={os.getcwd()}")
print(f"executable={sys.executable}")
print(f"version={sys.version.split()[0]}")
print(f"safepath={os.environ.get('PYTHONSAFEPATH', '')!r}")
print(f"pythonpath={os.environ.get('PYTHONPATH', '')!r}")
print(f"virtual_env={os.environ.get('VIRTUAL_ENV', '')!r}")
print(f"pid={os.getpid()}")
print(f"target={target}")
print(f"target_exists={target.is_file()}")
print(f"fence={fence_hash(target) if target.is_file() else 'missing-file'}")
print(f"git_head={git('rev-parse', 'HEAD')}")
print(f"git_branch={git('branch', '--show-current')}")
print("sys.path[0:5]=")
for entry in sys.path[:5]:
print(f" {entry}")
if __name__ == "__main__":
main()
Run the sidecar on the free server, then run it again in any other shell you were treating as equivalent. If you have a script runner locally, compare that prefix too, and do not assume the server has the same runner installed.
python3 runtime_id.py worker.py
ps aux | grep -E 'worker|python' | grep -v grep
stat worker.py
head -n 20 worker.py
If the executable lines differ, stop guessing package names, because you are not standing in the same runtime. If the fence hash is no-pep723-fence on one copy and a short digest on the other, you are not even debugging the same file. That printout is the whole lesson, without a dashboard.
Extract the hidden list instead of hallucinating one
This second snippet is also a labeled tool. It installs nothing and it freezes nothing. It only stops me from authoring a fake requirements.txt at two in the morning. It needs tomllib, so the extractor itself wants Python 3.11 or newer.
# extract_script_deps.py
from __future__ import annotations
import pathlib
import re
import sys
import tomllib
FENCE = re.compile(
r"^# /// script\n((?:#.*\n)+?)# ///",
re.MULTILINE,
)
def extract(path: pathlib.Path) -> dict:
text = path.read_text(encoding="utf-8")
match = FENCE.search(text)
if not match:
raise SystemExit(f"no PEP 723 fence in {path}")
lines = [re.sub(r"^# ?", "", raw) for raw in match.group(1).splitlines()]
return tomllib.loads("\n".join(lines))
def main() -> None:
path = pathlib.Path(sys.argv[1])
data = extract(path)
deps = data.get("dependencies", [])
requires = data.get("requires-python", "unspecified")
print(f"requires-python: {requires}")
if not deps:
print("dependencies: (none declared)")
return
print("dependencies:")
for dep in deps:
print(f" - {dep}")
print("--- pip-shaped ---")
print("\n".join(deps))
if __name__ == "__main__":
main()
python3 extract_script_deps.py worker.py
python3 extract_script_deps.py worker.py | sed -n '/pip-shaped/,$p' | tail -n +2 > requirements.from-fence.txt
cat requirements.from-fence.txt
I may still install those names into an environment I control. I just refuse to invent the list from traceback order or from a freeze taken on a different runner.
Decision table I wish I had printed on hour two
| Symptom on the free server | What it pretends to be | Check first | Do not do next |
|---|---|---|---|
ModuleNotFoundError, success in the chat |
"the server is missing pip packages" | PEP 723 fence, sys.executable, runner versus python3
|
Guess packages from import names alone |
| Import works, behavior looks stale | "the library is broken" |
pid, file realpath, git HEAD, boot uuid |
Tail a log without proving the writer |
| Fence exists, install still fails | "the free server is blocked" |
requires-python versus sys.version, extras, markers |
Pin whatever version pip grabs silently |
| Two terminals disagree about imports | "SSH is flaky today" |
cwd, PYTHONPATH, PYTHONSAFEPATH
|
Export random path hacks in both shells |
pip freeze looks complete |
"I should commit this lock" | Whether freeze came from the runner that read the fence | Copy a freeze across machines as truth |
A ten-minute test plan, not a victory lap
I did not collect public metrics for this weekend. I did write a plan I can rerun without storytelling.
- Copy
worker.py,runtime_id.py, andextract_script_deps.pyinto a clean directory on the free server. - Run
python3 worker.pyand record the traceback; a missing third-party import is the expected first failure. - Run
python3 extract_script_deps.py worker.pyand confirm the printed names match the fence, not your memory. - Run
python3 runtime_id.py worker.pyin the chat-adjacent runner and in SSH, then diff the executable lines. - Install only the extracted names into one environment you control, restart the worker, and print a boot uuid at start.
- Edit the fence, rerun the extractor, and confirm the fence hash changed before you argue about runtime behavior.
If step 4 is identical and step 2 still fails, then you may actually have a package problem worth fixing. If step 4 differs, you never had a package problem sitting in front of you. You had two computers, and one of them could read comments as manifests.
What I would repeat after the 48 hours
I would still use a free model to draft a small worker, because the edit loop stays fast when the file stays small. I would still use a free server so the process can outlive my laptop lid and a closed terminal. I would not treat a transcript exit code as evidence that python3 worker.py is a legal way to start that file.
I would print runtime identity before I install anything from a guess. I would extract the fence before I write requirements.txt by hand. I would give the worker a boot uuid so stale processes cannot impersonate new ones in the log. And I would ask one rude question out loud, every time: which interpreter, which file, which pid?
Limitations, and who should skip this
This workflow is for small scripts and single-process workers you can restart without a ceremony. It is not a packaging strategy for a team that already standardized on lockfiles and images. If your deploy path is Docker with a real pyproject.toml, you do not need a comment fence, and you should not add one as decoration.
PEP 723 readers are not interchangeable across machines. A command that understands the fence in a chat environment may be absent on the free server you actually keep running. I am not claiming any particular runner, quota, or Python build is waiting there. Check before you write notes that say "just run it," because that sentence hid my bug.
runtime_id.py will not catch a module imported from a similarly named file elsewhere on sys.path. It will not catch a yanked PyPI version or a marker you ignored. It will not catch the ugly case where the model wrote a fence and also a contradictory requirements.txt. When both exist, pick one source of truth and delete the other before the next install.
Do not use this approach if you need secrets in the script header. Dependency names are not secrets, but tired people paste tokens next to them anyway. Keep credentials in the environment, and keep the fence boring. Do not use this approach if you cannot restart the process you believe you are reading. And do not put data on a free remote box if you would not paste that data into a ticket.
The habit that survived the weekend is unglamorous, which is why I trust it more than another guessed install. Same command prefix, same file, printed identity, then extract, then install. If you already work in a free-model plus free-server loop, copy the sidecar before the next pip guess, not after the traceback trains you to improvise.
Top comments (0)