I opened the weekend convinced the function change was the entire defect, because every local test stayed green. The patch added one bare dependency and a short import guard, which is exactly the sort of diff I stop reading. Have you ever merged a harmless requirements line and then watched a clean machine disagree with your laptop? That disagreement is the failure I want written down before I trust the next one-line dependency add.
What I tried before I touched the server
I applied the suggestion inside my existing virtualenv, reran the unit tests, and treated the green run as permission to stop. The new requirements line held only a project name, with no version, no hash, and no comment about why it existed. I assumed the public index would hand me the same tree tomorrow, which is a comfortable assumption and a poor one. Why would a package index owe me yesterday's resolution just because my notebook can still import the name?
I then asked for a second reading of the diff, using MonkeyCode's free model access only as a reviewer.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
I use that access to pressure-test the wording of a diff, not to choose what the index should resolve. I use the free server option when I want a machine that does not already contain leftover site-packages. I do not treat either option as a promise about model names, quotas, hardware, or how long the offer stays up. The second reading did not produce a lockfile, and that silence was more useful than another confident import.
A model can sound sure about a name while knowing nothing about the index state on the day you install. If I let that confidence land in git, whose resolution am I actually committing, mine or the index's next upload? I would rather have an awkward question in the notes than a quiet dependency edit in the diff.
What broke after the clean install
On the second morning I cloned that same commit onto a fresh server and installed from the bare requirements file. The import that had passed locally failed there, because the resolver selected a different build than the one on my laptop. I blamed the function text for an hour before I compared the two freeze snapshots and saw the extra package drift. Have you noticed how quickly a green laptop becomes an alibi for a dependency you never pinned?
Three things were already true before I opened the traceback, and none of them lived in the function I kept rereading.
- My local virtualenv already held a transitive pin that the fresh server had never been given a chance to see.
- The requirements line named a project and trusted the index to define the word current at whatever hour I installed.
- I had no committed record of the index URL, the install timestamp, or the Python version sitting beside that diff.
The function change was boring once I stopped defending it, and the unpinned add was the commit I should have reviewed.
How I misread the traceback
The traceback named the import line, so I spent the next hour editing that guard as if the name itself were wrong. Was the name wrong, or was I looking at a module that existed only because my laptop had installed it last month? I almost pasted a version pin from the chat, which would have frozen a guess instead of a recorded install. That is the loop I would not repeat, because a suggestion can name a version it has not just installed.
Forty-eight hours here is my review window, not a claim about how long a free server remains available. I would rather lose an afternoon on a clean install than spend it arguing with a traceback from a dirty one. Would you keep the chat open and hope the index stays still, or would you write the freeze down while the environment still exists?
The audit I would rerun
I do not want another paragraph of intent in the pull request when a small file can record what the installer actually did. I want an audit I can run before I accept a dependency edit from any assistant, including a session that used free model access. The script below is a worked example you can copy, not a benchmark, and it stays inside the Python standard library on purpose. If you paste it into a repository, treat the index URL as a placeholder and point it at the index you really use.
Record the card
#!/usr/bin/env python3
"""Label: worked example, not a measured production run."""
import argparse
import hashlib
import json
import platform
import sys
from datetime import datetime, timezone
def freeze_lines():
lines = []
for raw in sys.stdin:
line = raw.strip()
if line and not line.startswith('-e '):
lines.append(line)
return lines
def main():
parser = argparse.ArgumentParser(
description='Record a resolver card from pip freeze on stdin.'
)
parser.add_argument('--index-url', required=True)
parser.add_argument('--out', default='resolve-card.json')
args = parser.parse_args()
lines = freeze_lines()
blob = '\n'.join(sorted(lines)).encode('utf-8')
card = {
'recorded_at': datetime.now(timezone.utc).isoformat(),
'python': sys.version.split()[0],
'implementation': platform.python_implementation(),
'platform': platform.platform(),
'index_url': args.index_url,
'frozen_count': len(lines),
'frozen_sha256': hashlib.sha256(blob).hexdigest(),
'frozen': lines,
}
with open(args.out, 'w', encoding='utf-8') as handle:
json.dump(card, handle, indent=2)
handle.write('\n')
digest = card['frozen_sha256'][:12]
print('wrote', args.out, 'count=' + str(card['frozen_count']), 'sha=' + digest)
if __name__ == '__main__':
main()
python -m venv .venv
. .venv/bin/activate
python -V
python -m pip --version
python -m pip install -r requirements.txt
python -m pip check
python -m pip freeze | python tools/resolve_card.py \
--index-url "https://pypi.org/simple" \
--out resolve-card.json
I run python -V and python -m pip --version first so the card is not the only place the interpreter can surprise me. pip check will not catch an unpinned future install, but it will tell me whether the tree I just created is already inconsistent. The freeze piped into the script is the snapshot I diff later, and I keep that JSON beside the notes.
On Windows I would run the Scripts activate file, and I would still call pip as python -m pip so the interpreter stays obvious. Replace the example index URL if your project uses a mirror, because a card from the wrong index is a false match. I would rather fail the audit on a wrong URL than explain a hash mismatch that was only a mirror lag.
Compare two cards
On the free server I would run those same commands in a new checkout, then diff the two JSON cards. I would not ask the model to remember the previous freeze, because a chat transcript is a weak place to store a hash. A file committed next to the patch is a better memory than a window I might close, isn't it?
#!/usr/bin/env python3
"""Label: comparison helper. Unexecuted until you pass two real cards."""
import json
import sys
def load(path):
with open(path, encoding='utf-8') as handle:
return json.load(handle)
def main():
if len(sys.argv) != 3:
print('usage: compare_cards.py LEFT.json RIGHT.json', file=sys.stderr)
return 1
left, right = load(sys.argv[1]), load(sys.argv[2])
mismatched = False
for key in ('python', 'index_url', 'frozen_sha256'):
same = left.get(key) == right.get(key)
print(key + ':', 'match' if same else 'DIFF')
if not same:
mismatched = True
print(' left=' + str(left.get(key)))
print(' right=' + str(right.get(key)))
return 2 if mismatched else 0
if __name__ == '__main__':
raise SystemExit(main())
python tools/compare_cards.py laptop.resolve-card.json server.resolve-card.json
echo "exit=$?"
The helper exits with status 2 when the frozen hashes differ, so a shell script can stop before I open the editor. I still read the printed lines, because a Python version mismatch can hide behind a hash that happened to match. If the index URLs differ, I treat the comparison as void and rerun both installs against the same index.
A decision table rather than a vibe
I keep a short table in the notes so I do not renegotiate the rule every time a suggestion sounds small. Would a docs-only change deserve this much friction, or am I protecting the one class of edit that rewrites the world? The table is the artifact I would paste above the script output, so the rule survives after I close the chat.
| Signal | What I do | What I refuse |
|---|---|---|
| Requirements line has no pin or hash | Stop and generate a lock or a hashed requirements file | Merging because local tests passed |
| Two clean installs disagree on frozen_sha256 | Keep the patch unmerged and pin the known-good set | Blaming the function diff first |
| Model suggests a new dependency | Ask it to explain the import, then record the card myself | Treating the suggestion as the resolution |
| Card is missing Python version or index URL | Rerun the audit before anyone reviews the traceback | Assuming the laptop and the server match |
I would not block a documentation typo with this table, because no installer runs when the diff never touches dependencies. I would block a one-line dependency add that arrived from a chat window until the second card matches the first card. If the hashes diverge, the function diff can wait, since arguing about a traceback before the trees match wastes the hour.
What I would repeat on the next patch
The order matters more than the particular tool, and I would repeat that order even if I swapped the installer later.
- Create the environment on the clean server first, before I reread the suggested function and start defending it.
- Install only from the committed requirements or lock, and write the index URL into the card beside the timestamp.
- Save one card from the laptop and one card from the fresh server, then diff the hashes before any review comment.
- Let the model critique the diff only after both cards exist, and discard advice that cannot point at those files.
- If the hashes diverge, pin or hash the dependency set and rerun both installs before I talk about the traceback.
The free server matters because it does not inherit my site-packages, and a clean failure is more honest than a dirty pass. The free model access matters because a second reading is cheap, not because the model has seen today's index state. Neither option selects a version for me, and neither option should be described as a permanent lab or a private mirror.
Limits, and who should skip this
This checklist does not prove that a package is safe, maintained, or free of yanked releases after you recorded the card. It does not replace a lockfile with hashes, a private index, or a separate review of license and security advisories. If your installer is Poetry, PDM, or uv, keep that tool's lock and treat the card as a cross-check rather than a rival. Do not run the audit against a production host, and do not paste secrets into the card, the shell history, or the prompt.
You should skip the approach if you cannot get a truly clean environment, because a fresh shell on a dirty image will lie. You should also skip it when the change does not touch dependencies, interpreters, system packages, or install flags at all. A floating pin is a process bug, and a model that agrees with my hurry is not a second engineer on the review.
The script ignores editable installs and does not verify hashes, so a matching count can still hide a different artifact. pip freeze records what was installed, not every marker or environment option that influenced the resolver. If you need a stronger gate, move to a lockfile that your installer can reproduce, and keep this card as the witness.
If you already open MonkeyCode for a second reading, point the free model at the diff and the free server at the install. Keep both cards next to the patch, because those notes are the part I would still trust on the following morning. I would stop there, rather than asking the model to invent a pin it cannot show me inside either card.
Top comments (0)